This commit is contained in:
Vladyslav Matsiiako
2023-04-20 21:29:05 -07:00
17 changed files with 1498 additions and 886 deletions

1854
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
{
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.301.0",
"@aws-sdk/client-secrets-manager": "^3.303.0",
"@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5",
"@sentry/node": "^7.45.0",
"@sentry/tracing": "^7.45.0",
"@sentry/tracing": "^7.46.0",
"@sentry/node": "^7.41.0",
"@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10",

View File

@@ -10,9 +10,11 @@ import (
"errors"
"fmt"
"net/url"
"regexp"
"github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/crypto"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/Infisical/infisical-merge/packages/srp"
@@ -33,6 +35,10 @@ type params struct {
keyLength uint32
}
const ADD_USER = "Add a new account login"
const REPLACE_USER = "Override current logged in user"
const EXIT_USER_MENU = "Exit"
// loginCmd represents the login command
var loginCmd = &cobra.Command{
Use: "login",
@@ -49,7 +55,7 @@ var loginCmd = &cobra.Command{
}
if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired && len(currentLoggedInUserDetails.UserCredentials.PrivateKey) != 0 {
shouldOverride, err := shouldOverrideLoginPrompt(currentLoggedInUserDetails.UserCredentials.Email)
shouldOverride, err := userLoginMenu(currentLoggedInUserDetails.UserCredentials.Email)
if err != nil {
util.HandleError(err)
}
@@ -59,6 +65,31 @@ var loginCmd = &cobra.Command{
}
}
//override domain
domainQuery := true
if config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL {
overrideDomain, err := DomainOverridePrompt()
if err != nil {
util.HandleError(err)
}
//if not override set INFISICAL_URL to exported var
//set domainQuery to false
if !overrideDomain {
domainQuery = false
config.INFISICAL_URL = config.INFISICAL_URL_MANUAL_OVERRIDE
}
}
//prompt user to select domain between Infisical cloud and self hosting
if domainQuery {
err = askForDomain()
if err != nil {
util.HandleError(err, "Unable to parse domain url")
}
}
email, password, err := askForLoginCredentials()
if err != nil {
util.HandleError(err, "Unable to parse email and password for authentication")
@@ -252,6 +283,77 @@ func init() {
rootCmd.AddCommand(loginCmd)
}
func DomainOverridePrompt() (bool, error) {
const (
PRESET = "Use Domain"
OVERRIDE = "Change Domain"
)
options := []string{PRESET, OVERRIDE}
optionsPrompt := promptui.Select{
Label: fmt.Sprintf("Current INFISICAL_API_URL Domain Override: %s", config.INFISICAL_URL_MANUAL_OVERRIDE),
Items: options,
Size: 2,
}
_, selectedOption, err := optionsPrompt.Run()
if err != nil {
return false, err
}
return selectedOption == OVERRIDE, err
}
func askForDomain() error {
//query user to choose between Infisical cloud or self hosting
const (
INFISICAL_CLOUD = "Infisical Cloud"
SELF_HOSTING = "Self Hosting"
)
options := []string{INFISICAL_CLOUD, SELF_HOSTING}
optionsPrompt := promptui.Select{
Label: "Select your hosting option",
Items: options,
Size: 2,
}
_, selectedHostingOption, err := optionsPrompt.Run()
if err != nil {
return err
}
if selectedHostingOption == INFISICAL_CLOUD {
//cloud option
config.INFISICAL_URL = util.INFISICAL_DEFAULT_API_URL
return nil
}
urlValidation := func(input string) error {
_, err := url.ParseRequestURI(input)
if err != nil {
return errors.New("this is an invalid url")
}
return nil
}
domainPrompt := promptui.Prompt{
Label: "Domain",
Validate: urlValidation,
Default: "Example - https://my-self-hosted-instance.com/api",
}
domain, err := domainPrompt.Run()
if err != nil {
return err
}
//set api url
config.INFISICAL_URL = domain
//return nil
return nil
}
func askForLoginCredentials() (email string, password string, err error) {
validateEmail := func(input string) error {
matched, err := regexp.MatchString("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", input)
@@ -342,16 +444,18 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R
return &loginOneResponseResult, &loginTwoResponseResult, nil
}
func shouldOverrideLoginPrompt(currentLoggedInUserEmail string) (bool, error) {
func userLoginMenu(currentLoggedInUserEmail string) (bool, error) {
label := fmt.Sprintf("Current logged in user email: %s on domain: %s", currentLoggedInUserEmail, config.INFISICAL_URL)
prompt := promptui.Select{
Label: fmt.Sprintf("There seems to be a user already logged in with the email: %s. Would you like to override that login? Select[Yes/No]", currentLoggedInUserEmail),
Items: []string{"No", "Yes"},
Label: label,
Items: []string{ADD_USER, REPLACE_USER, EXIT_USER_MENU},
}
_, result, err := prompt.Run()
if err != nil {
return false, err
}
return result == "Yes", err
return result != EXIT_USER_MENU, err
}
func generateFromPassword(password string, salt []byte, p *params) (hash []byte, err error) {

252
cli/packages/cmd/user.go Normal file
View File

@@ -0,0 +1,252 @@
package cmd
import (
"errors"
"net/url"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
)
var userCmd = &cobra.Command{
Use: "user",
Short: "Used to manage user credentials",
DisableFlagsInUseLine: true,
Example: "infisical user",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
var switchCmd = &cobra.Command{
Use: "switch",
Short: "Used to switch between Infisical profiles",
DisableFlagsInUseLine: true,
Example: "infisical switch",
Args: cobra.ExactArgs(0),
PreRun: func(cmd *cobra.Command, args []string) {
util.RequireLogin()
},
Run: func(cmd *cobra.Command, args []string) {
//get previous logged in profiles
loggedInProfiles, err := getLoggedInUsers()
if err != nil {
util.HandleError(err, "[infisical user switch]: Unable to get logged Profiles")
}
//prompt user
profile, err := LoggedInUsersPrompt(loggedInProfiles)
if err != nil {
util.HandleError(err, "[infisical user switch]: Prompt error")
}
//write to config file
configFile, err := util.GetConfigFile()
if err != nil {
util.HandleError(err, "[infisical user switch]: Unable to get config file")
}
configFile.LoggedInUserEmail = profile
//set logged in user domain
ok := util.ConfigContainsEmail(configFile.LoggedInUsers, profile)
if !ok {
//profile not in loggedInUsers
configFile.LoggedInUsers = append(configFile.LoggedInUsers, models.LoggedInUser{
Email: profile,
Domain: config.INFISICAL_URL,
})
//set logged in user domain
configFile.LoggedInUserDomain = config.INFISICAL_URL
} else {
//exists, set logged in user domain
for _, v := range configFile.LoggedInUsers {
if profile == v.Email {
configFile.LoggedInUserDomain = v.Domain
break
}
}
}
err = util.WriteConfigFile(&configFile)
if err != nil {
util.HandleError(err, "")
}
},
}
var updateCmd = &cobra.Command{
Use: "update",
Short: "Used to update properties of an Infisical profile",
DisableFlagsInUseLine: true,
Example: "infisical user update",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
var domainCmd = &cobra.Command{
Use: "domain",
Short: "Used to update the domain of an Infisical profile",
DisableFlagsInUseLine: true,
Example: "infisical user update domain",
Args: cobra.ExactArgs(0),
PreRun: func(cmd *cobra.Command, args []string) {
util.RequireLogin()
},
Run: func(cmd *cobra.Command, args []string) {
//prompt for profiles selection
loggedInProfiles, err := getLoggedInUsers()
if err != nil {
util.HandleError(err, "[infisical user update domain]: Unable to get logged Profiles")
}
//prompt user
profile, err := LoggedInUsersPrompt(loggedInProfiles)
if err != nil {
util.HandleError(err, "[infisical user update domain]: Prompt error")
}
domain := ""
domainQuery := true
if config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL {
override, err := DomainOverridePrompt()
if err != nil {
util.HandleError(err, "[infisical user update domain]: Domain override prompt error")
}
if !override {
domainQuery = false
domain = config.INFISICAL_URL_MANUAL_OVERRIDE
}
}
if domainQuery {
//prompt to update domain
domain, err = NewDomainPrompt()
if err != nil {
util.HandleError(err, "[infisical user update domain]: Prompt error")
}
}
//write to config file
configFile, err := util.GetConfigFile()
if err != nil {
util.HandleError(err, "[infisical user update domain]: Unable to get config file")
}
//check if profile in logged in profiles
//if not add new profile loggedInUsers
//else update profile from loggedinUsers slice
ok := util.ConfigContainsEmail(configFile.LoggedInUsers, profile)
if !ok {
configFile.LoggedInUsers = append(configFile.LoggedInUsers, models.LoggedInUser{
Email: profile,
Domain: domain,
})
} else {
//exists, set logged in user domain
for idx, v := range configFile.LoggedInUsers {
if profile == v.Email {
configFile.LoggedInUsers[idx].Domain = domain //inplace
break
}
}
}
//check if current loggedinuser is selected profile
//if yes set current domain to changed domain
if configFile.LoggedInUserEmail == profile {
configFile.LoggedInUserDomain = domain
}
err = util.WriteConfigFile(&configFile)
if err != nil {
util.HandleError(err, "")
}
},
}
func init() {
updateCmd.AddCommand(domainCmd)
userCmd.AddCommand(updateCmd)
userCmd.AddCommand(switchCmd)
rootCmd.AddCommand(userCmd)
}
// This returns all logged in user emails from the config file.
// If none, it returns the current logged in user in a slice
func getLoggedInUsers() ([]string, error) {
loggedInProfiles := []string{}
if util.ConfigFileExists() {
configFile, err := util.GetConfigFile()
if err != nil {
return loggedInProfiles, err
}
//get logged in profiles
//
if len(configFile.LoggedInUsers) > 0 {
for _, v := range configFile.LoggedInUsers {
loggedInProfiles = append(loggedInProfiles, v.Email)
}
} else {
loggedInProfiles = append(loggedInProfiles, configFile.LoggedInUserEmail)
}
return loggedInProfiles, nil
} else {
//empty
return loggedInProfiles, errors.New("couldn't retrieve config file")
}
}
func NewDomainPrompt() (string, error) {
urlValidation := func(input string) error {
_, err := url.ParseRequestURI(input)
if err != nil {
return errors.New("this is an invalid url")
}
return nil
}
//else run prompt to enter domain
domainPrompt := promptui.Prompt{
Label: "New Domain",
Validate: urlValidation,
Default: "Example - https://my-self-hosted-instance.com/api",
}
domain, err := domainPrompt.Run()
if err != nil {
return "", err
}
return domain, nil
}
func LoggedInUsersPrompt(profiles []string) (string, error) {
prompt := promptui.Select{Label: "Which of your Infisical profiles would you like to use",
Items: profiles,
Size: 7,
}
idx, _, err := prompt.Run()
if err != nil {
return "", err
}
return profiles[idx], nil
}

View File

@@ -1,3 +1,4 @@
package config
var INFISICAL_URL string
var INFISICAL_URL_MANUAL_OVERRIDE string

View File

@@ -12,8 +12,15 @@ type UserCredentials struct {
// The file struct for Infisical config file
type ConfigFile struct {
LoggedInUserEmail string `json:"loggedInUserEmail"`
VaultBackendType keyring.BackendType `json:"vaultBackendType"`
LoggedInUserEmail string `json:"loggedInUserEmail"`
LoggedInUserDomain string `json:"LoggedInUserDomain,omitempty"`
VaultBackendType keyring.BackendType `json:"vaultBackendType"`
LoggedInUsers []LoggedInUser `json:"loggedInUsers,omitempty"`
}
type LoggedInUser struct {
Email string `json:"email"`
Domain string `json:"domain"`
}
type SingleEnvironmentVariable struct {

View File

@@ -23,8 +23,5 @@ func WriteToFile(fileName string, dataToWrite []byte, filePerm os.FileMode) erro
func CheckIsConnectedToInternet() (ok bool) {
_, err := http.Get("http://clients3.google.com/generate_204")
if err != nil {
return false
}
return true
return err == nil
}

View File

@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/models"
log "github.com/sirupsen/logrus"
)
@@ -31,9 +32,28 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error {
return fmt.Errorf("writeInitalConfig: unable to write config file because [err=%s]", err)
}
//if profiles exists
loggedInUser := models.LoggedInUser{
Email: userCredentials.Email,
Domain: config.INFISICAL_URL,
}
//if empty or if email not in loggedinUsers
if len(existingConfigFile.LoggedInUsers) == 0 || !ConfigContainsEmail(existingConfigFile.LoggedInUsers, userCredentials.Email) {
existingConfigFile.LoggedInUsers = append(existingConfigFile.LoggedInUsers, loggedInUser)
} else {
//if exists update domain of loggedin users
for idx, user := range existingConfigFile.LoggedInUsers {
if user.Email == userCredentials.Email {
existingConfigFile.LoggedInUsers[idx] = loggedInUser
}
}
}
configFile := models.ConfigFile{
LoggedInUserEmail: userCredentials.Email,
VaultBackendType: existingConfigFile.VaultBackendType,
LoggedInUserEmail: userCredentials.Email,
LoggedInUserDomain: config.INFISICAL_URL,
VaultBackendType: existingConfigFile.VaultBackendType,
LoggedInUsers: existingConfigFile.LoggedInUsers,
}
configFileMarshalled, err := json.Marshal(configFile)
@@ -176,7 +196,7 @@ func GetConfigFile() (models.ConfigFile, error) {
return configFile, nil
}
// Write a ConfigFile to disk. Raise error if unable to save the model to ask
// Write a ConfigFile to disk. Raise error if unable to save the model to disk
func WriteConfigFile(configFile *models.ConfigFile) error {
fullConfigFilePath, fullConfigFileDirPath, err := GetFullConfigFilePath()
if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"github.com/99designs/keyring"
"github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/go-resty/resty/v2"
)
@@ -87,6 +88,13 @@ func GetCurrentLoggedInUserDetails() (LoggedInUserDetails, error) {
SetAuthToken(userCreds.JTWToken).
SetHeader("Accept", "application/json")
config.INFISICAL_URL_MANUAL_OVERRIDE = config.INFISICAL_URL
//configFile.LoggedInUserDomain
//if not empty set as infisical url
if configFile.LoggedInUserDomain != "" {
config.INFISICAL_URL = configFile.LoggedInUserDomain
}
isAuthenticated := api.CallIsAuthenticated(httpClient)
if !isAuthenticated {
return LoggedInUserDetails{

View File

@@ -9,6 +9,8 @@ import (
"os/exec"
"path"
"strings"
"github.com/Infisical/infisical-merge/packages/models"
)
type DecodedSymmetricEncryptionDetails = struct {
@@ -61,6 +63,16 @@ func IsSecretTypeValid(s string) bool {
return false
}
// Checks if the passed in email already exists in the users slice
func ConfigContainsEmail(users []models.LoggedInUser, email string) bool {
for _, value := range users {
if value.Email == email {
return true
}
}
return false
}
func RequireLogin() {
currentUserDetails, err := GetCurrentLoggedInUserDetails()

View File

@@ -17,10 +17,10 @@ import (
"github.com/go-resty/resty/v2"
)
func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.SingleEnvironmentVariable, error) {
func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.SingleEnvironmentVariable, api.GetServiceTokenDetailsResponse, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 4 {
return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
}
serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2])
@@ -32,7 +32,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.Singl
serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient)
if err != nil {
return nil, fmt.Errorf("unable to get service token details. [err=%v]", err)
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to get service token details. [err=%v]", err)
}
encryptedSecrets, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
@@ -41,25 +41,25 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.Singl
})
if err != nil {
return nil, err
return nil, api.GetServiceTokenDetailsResponse{}, err
}
decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag)
if err != nil {
return nil, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err)
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err)
}
plainTextWorkspaceKey, err := crypto.DecryptSymmetric([]byte(serviceTokenParts[3]), decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV)
if err != nil {
return nil, fmt.Errorf("unable to decrypt the required workspace key")
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decrypt the required workspace key")
}
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecrets)
if err != nil {
return nil, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
}
return plainTextSecrets, nil
return plainTextSecrets, serviceTokenDetails, nil
}
func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, workspaceId string, environmentName string, tagSlugs string) ([]models.SingleEnvironmentVariable, error) {
@@ -131,6 +131,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
isConnected := CheckIsConnectedToInternet()
var secretsToReturn []models.SingleEnvironmentVariable
var serviceTokenDetails api.GetServiceTokenDetailsResponse
var errorToReturn error
if infisicalToken == "" {
@@ -182,7 +183,11 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
} else {
log.Debug("Trying to fetch secrets using service token")
secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(infisicalToken)
secretsToReturn, serviceTokenDetails, errorToReturn = GetPlainTextSecretsViaServiceToken(infisicalToken)
if serviceTokenDetails.Environment != params.Environment {
PrintErrorMessageAndExit(fmt.Sprintf("Fetch secrets failed: token allows [%s] environment access, not [%s]. Service tokens are environment-specific; no need for --env flag.", params.Environment, serviceTokenDetails.Environment))
}
}
return secretsToReturn, errorToReturn

View File

@@ -9,4 +9,7 @@ infisical login
## Description
The CLI uses authentication to verify your identity. When you enter the correct email and password for your account, a token is generated and saved in your system Keyring to allow you to make future interactions with the CLI.
If you want to change where the login credentials are stored, visit the [vaults command](./vault)
To change where the login credentials are stored, visit the [vaults command](./vault).
If you have added multiple users, you can switch between the users by using the [user command](./user).

View File

@@ -0,0 +1,29 @@
---
title: "infisical user"
description: "Manage logged in users"
---
```bash
infisical user
```
## Description
This command allows you to manage the current logged in users on the CLI
### Sub-commands
<Accordion title="infisical user switch" defaultOpen="true">
Use this command to switch between profiles that are currently logged into the CLI
```bash
infisical user switch
```
</Accordion>
<Accordion title="infisical user update domain">
With this command, you can modify the backend API that is utilized for all requests associated with a specific profile.
For instance, you have the option to point the profile to use either the Infisical Cloud or your own self-hosted Infisical instance.
```bash
infisical user update domain
```
</Accordion>

View File

@@ -1,6 +1,6 @@
---
title: "FAQ"
description: "Frequently Asked Questions about Infisical"
description: "Frequently Asked Questions about Infisical CLI"
---
Frequently asked questions about the CLI can be found on this page.

View File

@@ -109,7 +109,10 @@ infisical login
<Accordion title="Optional: point CLI to self-hosted">
The CLI is set to connect to Infisical Cloud by default, but if you're running your own instance of Infisical, you can direct the CLI to it using one of the methods provided below.
#### Method 1: Export environment variable
#### Method 1: Use the updated CLI
Beginning with CLI version V0.4.0, it is now possible to choose between logging in through the Infisical cloud or your own self-hosted instance. Simply execute the `infisical login` command and follow the on-screen instructions.
#### Method 2: Export environment variable
You can point the CLI to the self hosted Infisical instance by exporting the environment variable `INFISICAL_API_URL` in your terminal.
<Tabs>
@@ -135,7 +138,7 @@ You can point the CLI to the self hosted Infisical instance by exporting the env
</Tab>
</Tabs>
#### Method 2: Set manually on every command
#### Method 3: Set manually on every command
Another option to point the CLI to your self hosted Infisical instance is to set it via a flag on every command you run.
```bash

View File

@@ -101,7 +101,8 @@
"pages": [
"self-hosting/overview",
"self-hosting/configuration/envars",
"self-hosting/configuration/email"
"self-hosting/configuration/email",
"self-hosting/faq"
]
},
{
@@ -118,6 +119,7 @@
"cli/commands/secrets",
"cli/commands/export",
"cli/commands/vault",
"cli/commands/user",
"cli/commands/reset"
]
},

27
docs/self-hosting/faq.mdx Normal file
View File

@@ -0,0 +1,27 @@
---
title: "FAQ"
description: "Frequently Asked Questions about Infisical self hosting"
---
Frequently asked questions about self hosted instance of Infisical can be found on this page.
If you can't find the answer you are looking for, please create an issue on our GitHub repository or join our Slack channel for additional support.
<Accordion title="When I refresh any page, it logs me out" defaultOpen="true">
This issue is typically seen when you haven't set up SSL for your self hosted instance of Infisical. When SSL is not enabled, you can't receive secure cookies, preventing the session data to not be saved.
To fix this, we highly recommend that you set up SSL for your instance.
However, in the event you choose to use Infisical without SSL, you can do so by setting the `HTTPS_ENABLED` environment variable to `"false"` for the backend application.
[Learn more about secure cookies](https://really-simple-ssl.com/definition/what-are-secure-cookies/)
</Accordion>
<Accordion title="Is self hosted Infisical HA?">
Infisical leverages the robust container orchestration capabilities of Kubernetes and the inherent high availability features of Bitnami MongoDB to ensure resilience and fault tolerance.
By deploying multiple replicas of Infisical application on Kubernetes, operations can continue even if a single instance fails.
Additionally, Bitnami MongoDB supports replica sets, which provide data redundancy and automatic failover for the underlying database.
Kubernetes Services facilitate load balancing, effectively distributing traffic across your application's instances and ensuring optimal performance.
The combination of Kubernetes' self-healing mechanisms and Bitnami MongoDB's failover capabilities work together to create a highly available and fault-tolerant application capable of recovering gracefully from unexpected failures.
To further increase data redundancy, we recommend that you use a managed MongoDB service for your self hosted instance of Infisical.
</Accordion>