1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-22 14:05:22 +00:00

Compare commits

..

18 Commits

Author SHA1 Message Date
65f122bd41 Update index.cjs 2024-11-16 01:37:43 +04:00
4acf9413f0 Merge pull request from Infisical/backfill-identity-metadata
Fix: Handle Missing User/Identity Metadata Keys in Permissions Check
2024-11-15 01:34:45 -07:00
f0549cab98 Merge pull request from Infisical/fix-ca-alert-migrations
only create triggers when create new table
2024-11-15 00:56:39 -07:00
d75e49dce5 update trigegr to only create if it doesn't exit 2024-11-15 00:52:08 -07:00
8819abd710 only create triggers when create new table 2024-11-15 00:42:30 -07:00
796f76da46 Merge pull request from Infisical/fix-cert-migration
Fix ca version migration
2024-11-14 23:20:09 -07:00
d6e1ed4d1e revert docker compose changes 2024-11-14 23:10:54 -07:00
1295b68d80 Fix ca version migration
We didn't do a check to see if the column already exists. Because of this, we get this error during migrations:

```
| migration file "20240802181855_ca-cert-version.ts" failed
infisical-db-migration  | migration failed with error: alter table "certificates" add column "caCertId" uuid null - column "caCertId" of relation "certificates" already exists
```
2024-11-14 23:07:30 -07:00
c79f84c064 fix: use proxy on metadata permissions check to handle missing keys 2024-11-14 11:36:07 -08:00
d0c50960ef Merge pull request from Infisical/doc/add-gitlab-oidc-auth-documentation
doc: add docs for gitlab oidc auth
2024-11-14 10:44:01 -07:00
85089a08e1 Merge pull request from Infisical/misc/update-login-self-hosting-label
misc: updated login self-hosting label to include dedicated
2024-11-15 01:41:45 +08:00
bf97294dad misc: added idp label 2024-11-15 01:41:20 +08:00
4053078d95 misc: updated login self-hosting label for dedicated 2024-11-15 01:36:33 +08:00
4ba3899861 doc: add docs for gitlab oidc auth 2024-11-15 01:07:36 +08:00
ccad684ab2 Merge pull request from Infisical/docs-for-linux-ha
linux HA reference architecture
2024-11-14 02:04:13 -07:00
fd77708cad add docs for linux ha 2024-11-14 02:02:23 -07:00
9aebd712d1 Merge pull request from Infisical/daniel/npm-cli-fixes
fix: cli npm release windows and symlink bugs
2024-11-13 20:58:22 -07:00
05f07b25ac fix: cli npm release windows and symlink bugs 2024-11-14 06:13:14 +04:00
14 changed files with 717 additions and 575 deletions
backend/src
cli/packages/cmd
docker-compose.prod.yml
docs
documentation/platform/identities/oidc-auth
mint.json
self-hosting
deployment-options/native
reference-architectures
npm

@ -64,23 +64,25 @@ export async function up(knex: Knex): Promise<void> {
}
if (await knex.schema.hasTable(TableName.Certificate)) {
await knex.schema.alterTable(TableName.Certificate, (t) => {
t.uuid("caCertId").nullable();
t.foreign("caCertId").references("id").inTable(TableName.CertificateAuthorityCert);
});
const hasCaCertIdColumn = await knex.schema.hasColumn(TableName.Certificate, "caCertId");
if (!hasCaCertIdColumn) {
await knex.schema.alterTable(TableName.Certificate, (t) => {
t.uuid("caCertId").nullable();
t.foreign("caCertId").references("id").inTable(TableName.CertificateAuthorityCert);
});
await knex.raw(`
await knex.raw(`
UPDATE "${TableName.Certificate}" cert
SET "caCertId" = (
SELECT caCert.id
FROM "${TableName.CertificateAuthorityCert}" caCert
WHERE caCert."caId" = cert."caId"
)
`);
)`);
await knex.schema.alterTable(TableName.Certificate, (t) => {
t.uuid("caCertId").notNullable().alter();
});
await knex.schema.alterTable(TableName.Certificate, (t) => {
t.uuid("caCertId").notNullable().alter();
});
}
}
}

@ -2,6 +2,9 @@ import { Knex } from "knex";
import { TableName } from "./schemas";
interface PgTriggerResult {
rows: Array<{ exists: boolean }>;
}
export const createJunctionTable = (knex: Knex, tableName: TableName, table1Name: TableName, table2Name: TableName) =>
knex.schema.createTable(tableName, (table) => {
table.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
@ -28,13 +31,26 @@ DROP FUNCTION IF EXISTS on_update_timestamp() CASCADE;
// we would be using this to apply updatedAt where ever we wanta
// remember to set `timestamps(true,true,true)` before this on schema
export const createOnUpdateTrigger = (knex: Knex, tableName: string) =>
knex.raw(`
CREATE TRIGGER "${tableName}_updatedAt"
BEFORE UPDATE ON ${tableName}
FOR EACH ROW
EXECUTE PROCEDURE on_update_timestamp();
`);
export const createOnUpdateTrigger = async (knex: Knex, tableName: string) => {
const triggerExists = await knex.raw<PgTriggerResult>(`
SELECT EXISTS (
SELECT 1
FROM pg_trigger
WHERE tgname = '${tableName}_updatedAt'
);
`);
if (!triggerExists?.rows?.[0]?.exists) {
return knex.raw(`
CREATE TRIGGER "${tableName}_updatedAt"
BEFORE UPDATE ON ${tableName}
FOR EACH ROW
EXECUTE PROCEDURE on_update_timestamp();
`);
}
return null;
};
export const dropOnUpdateTrigger = (knex: Knex, tableName: string) =>
knex.raw(`DROP TRIGGER IF EXISTS "${tableName}_updatedAt" ON ${tableName}`);

@ -29,4 +29,18 @@ function validateOrgSSO(actorAuthMethod: ActorAuthMethod, isOrgSsoEnforced: TOrg
}
}
export { isAuthMethodSaml, validateOrgSSO };
const escapeHandlebarsMissingMetadata = (obj: Record<string, string>) => {
const handler = {
get(target: Record<string, string>, prop: string) {
if (!(prop in target)) {
// eslint-disable-next-line no-param-reassign
target[prop] = `{{identity.metadata.${prop}}}`; // Add missing key as an "own" property
}
return target[prop];
}
};
return new Proxy(obj, handler);
};
export { escapeHandlebarsMissingMetadata, isAuthMethodSaml, validateOrgSSO };

@ -21,7 +21,7 @@ import { TServiceTokenDALFactory } from "@app/services/service-token/service-tok
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
import { TPermissionDALFactory } from "./permission-dal";
import { validateOrgSSO } from "./permission-fns";
import { escapeHandlebarsMissingMetadata, validateOrgSSO } from "./permission-fns";
import { TBuildOrgPermissionDTO, TBuildProjectPermissionDTO } from "./permission-service-types";
import {
buildServiceTokenProjectPermission,
@ -227,11 +227,13 @@ export const permissionServiceFactory = ({
})) || [];
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false, strict: true });
const metadataKeyValuePair = objectify(
userProjectPermission.metadata,
(i) => i.key,
(i) => i.value
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
objectify(
userProjectPermission.metadata,
(i) => i.key,
(i) => i.value
)
);
const interpolateRules = templatedRules(
{
@ -292,12 +294,15 @@ export const permissionServiceFactory = ({
})) || [];
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false, strict: true });
const metadataKeyValuePair = objectify(
identityProjectPermission.metadata,
(i) => i.key,
(i) => i.value
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
objectify(
identityProjectPermission.metadata,
(i) => i.key,
(i) => i.value
)
);
const interpolateRules = templatedRules(
{
identity: {

@ -532,7 +532,7 @@ func askForDomain() error {
const (
INFISICAL_CLOUD_US = "Infisical Cloud (US Region)"
INFISICAL_CLOUD_EU = "Infisical Cloud (EU Region)"
SELF_HOSTING = "Self-Hosting"
SELF_HOSTING = "Self-Hosting or Dedicated Instance"
ADD_NEW_DOMAIN = "Add a new domain"
)

@ -69,4 +69,4 @@ volumes:
driver: local
networks:
infisical:
infisical:

@ -0,0 +1,145 @@
---
title: GitLab
description: "Learn how to authenticate GitLab pipelines with Infisical using OpenID Connect (OIDC)."
---
**OIDC Auth** is a platform-agnostic JWT-based authentication method that can be used to authenticate from any platform or environment using an identity provider with OpenID Connect.
## Diagram
The following sequence diagram illustrates the OIDC Auth workflow for authenticating GitLab pipelines with Infisical.
```mermaid
sequenceDiagram
participant Client as GitLab Pipeline
participant Idp as GitLab Identity Provider
participant Infis as Infisical
Client->>Idp: Step 1: Request identity token
Idp-->>Client: Return JWT with verifiable claims
Note over Client,Infis: Step 2: Login Operation
Client->>Infis: Send signed JWT to /api/v1/auth/oidc-auth/login
Note over Infis,Idp: Step 3: Query verification
Infis->>Idp: Request JWT public key using OIDC Discovery
Idp-->>Infis: Return public key
Note over Infis: Step 4: JWT validation
Infis->>Client: Return short-lived access token
Note over Client,Infis: Step 5: Access Infisical API with Token
Client->>Infis: Make authenticated requests using the short-lived access token
```
## Concept
At a high-level, Infisical authenticates a client by verifying the JWT and checking that it meets specific requirements (e.g. it is issued by a trusted identity provider) at the `/api/v1/auth/oidc-auth/login` endpoint. If successful,
then Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API.
To be more specific:
1. The GitLab pipeline requests an identity token from GitLab's identity provider.
2. The fetched identity token is sent to Infisical at the `/api/v1/auth/oidc-auth/login` endpoint.
3. Infisical fetches the public key that was used to sign the identity token from GitLab's identity provider using OIDC Discovery.
4. Infisical validates the JWT using the public key provided by the identity provider and checks that the subject, audience, and claims of the token matches with the set criteria.
5. If all is well, Infisical returns a short-lived access token that the GitLab pipeline can use to make authenticated requests to the Infisical API.
<Note>
Infisical needs network-level access to GitLab's identity provider endpoints.
</Note>
## Guide
In the following steps, we explore how to create and use identities to access the Infisical API using the OIDC Auth authentication method.
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)
When creating an identity, you specify an organization level [role](/documentation/platform/role-based-access-controls) for it to assume; you can configure roles in Organization Settings > Access Control > Organization Roles.
![identities organization create](/images/platform/identities/identities-org-create.png)
Now input a few details for your new identity. Here's some guidance for each field:
- Name (required): A friendly name for the identity.
- Role (required): A role from the **Organization Roles** tab for the identity to assume. The organization role assigned will determine what organization level resources this identity can have access to.
Once you've created an identity, you'll be redirected to a page where you can manage the identity.
![identities page](/images/platform/identities/identities-page.png)
Since the identity has been configured with Universal Auth by default, you should re-configure it to use OIDC Auth instead. To do this, press to edit the **Authentication** section,
remove the existing Universal Auth configuration, and add a new OIDC Auth configuration onto the identity.
![identities page remove default auth](/images/platform/identities/identities-page-remove-default-auth.png)
![identities create oidc auth method](/images/platform/identities/identities-org-create-oidc-auth-method.png)
<Warning>Restrict access by configuring the Subject, Audiences, and Claims fields</Warning>
Here's some more guidance on each field:
- OIDC Discovery URL: The URL used to retrieve the OpenID Connect configuration from the identity provider. This will be used to fetch the public key needed for verifying the provided JWT. For GitLab SaaS (GitLab.com), this should be set to `https://gitlab.com`. For self-hosted GitLab instances, use the domain of your GitLab instance.
- Issuer: The unique identifier of the identity provider issuing the JWT. This value is used to verify the iss (issuer) claim in the JWT to ensure the token is issued by a trusted provider. This should also be set to the domain of the Gitlab instance.
- CA Certificate: The PEM-encoded CA cert for establishing secure communication with the Identity Provider endpoints. For GitLab.com, this can be left blank.
- Subject: The expected principal that is the subject of the JWT. For GitLab pipelines, this should be set to a string that uniquely identifies the pipeline and its context, in the format `project_path:{group}/{project}:ref_type:{type}:ref:{branch_name}` (e.g., `project_path:example-group/example-project:ref_type:branch:ref:main`).
- Claims: Additional information or attributes that should be present in the JWT for it to be valid. You can refer to GitLab's [documentation](https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html#token-payload) for the list of supported claims.
- Access Token TTL (default is `2592000` equivalent to 30 days): The lifetime for an acccess token in seconds. This value will be referenced at renewal time.
- Access Token Max TTL (default is `2592000` equivalent to 30 days): The maximum lifetime for an acccess token in seconds. This value will be referenced at renewal time.
- Access Token Max Number of Uses (default is `0`): The maximum number of times that an access token can be used; a value of `0` implies infinite number of uses.
- Access Token Trusted IPs: The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the `0.0.0.0/0`, allowing usage from any network address.
<Tip>For more details on the appropriate values for the OIDC fields, refer to GitLab's [documentation](https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html#token-payload). </Tip>
<Info>The `subject`, `audiences`, and `claims` fields support glob pattern matching; however, we highly recommend using hardcoded values whenever possible.</Info>
</Step>
<Step title="Adding an identity to a project">
To enable the identity to access project-level resources such as secrets within a specific project, you should add it to that project.
To do this, head over to the project you want to add the identity to and go to Project Settings > Access Control > Machine Identities and press **Add identity**.
Next, select the identity you want to add to the project and the project level role you want to allow it to assume. The project role assigned will determine what project level resources this identity can have access to.
![identities project](/images/platform/identities/identities-project.png)
![identities project create](/images/platform/identities/identities-project-create.png)
</Step>
<Step title="Accessing the Infisical API with the identity">
As demonstration, we will be using the Infisical CLI to fetch Infisical secrets and utilize them within a GitLab pipeline.
To access Infisical secrets as the identity, you need to use an identity token from GitLab which matches the OIDC configuration defined for the machine identity.
This can be done by defining the `id_tokens` property. The resulting token would then be used to login with OIDC like the following: `infisical login --method=oidc-auth --oidc-jwt=$GITLAB_TOKEN`
Below is a complete example of how a GitLab pipeline can be configured to work with secrets from Infisical using the Infisical CLI with OIDC Auth:
```yaml
image: ubuntu
stages:
- build
build-job:
stage: build
id_tokens:
INFISICAL_ID_TOKEN:
aud: infisical-aud-test
script:
- apt update && apt install -y curl
- curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash
- apt-get update && apt-get install -y infisical
- export INFISICAL_TOKEN=$(infisical login --method=oidc-auth --machine-identity-id=4e807a78-1b1c-4bd6-9609-ef2b0cf4fd54 --oidc-jwt=$INFISICAL_ID_TOKEN --silent --plain)
- infisical run --projectId=1d0443c1-cd43-4b3a-91a3-9d5f81254a89 --env=dev -- npm run build
```
The `id_tokens` keyword is used to request an ID token for the job. In this example, an ID token named `INFISICAL_ID_TOKEN` is requested with the audience (`aud`) claim set to "infisical-aud-test". This ID token will be used to authenticate with Infisical.
<Note>
Each identity access token has a time-to-live (TTL) which you can infer from the response of the login operation; the default TTL is `7200` seconds, which can be adjusted.
If an identity access token expires, it can no longer authenticate with the Infisical API. In this case, a new access token should be obtained by performing another login operation.
</Note>
</Step>
</Steps>

@ -226,7 +226,8 @@
"pages": [
"documentation/platform/identities/oidc-auth/general",
"documentation/platform/identities/oidc-auth/github",
"documentation/platform/identities/oidc-auth/circleci"
"documentation/platform/identities/oidc-auth/circleci",
"documentation/platform/identities/oidc-auth/gitlab"
]
},
"documentation/platform/mfa",
@ -292,7 +293,10 @@
},
{
"group": "Reference architectures",
"pages": ["self-hosting/reference-architectures/aws-ecs"]
"pages": [
"self-hosting/reference-architectures/aws-ecs",
"self-hosting/reference-architectures/linux-deployment-ha"
]
},
"self-hosting/ee",
"self-hosting/faq"

@ -1,520 +0,0 @@
---
title: "Automatically deploy Infisical with High Availability"
sidebarTitle: "High Availability"
---
# Self-Hosting Infisical with a native High Availability (HA) deployment
This page describes the Infisical architecture designed to provide high availability (HA) and how to deploy Infisical with high availability. The high availability deployment is designed to ensure that Infisical services are always available and can handle service failures gracefully, without causing service disruptions.
<Info>
This deployment option is currently only available for Debian-based nodes (e.g., Ubuntu, Debian).
We plan on adding support for other operating systems in the future.
</Info>
## High availability architecture
| Service | Nodes | Configuration | GCP | AWS |
|----------------------------------|----------------|------------------------------|---------------|--------------|
| External load balancer$^1$ | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | c5n.xlarge |
| Internal load balancer$^2$ | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | c5n.xlarge |
| Etcd cluster$^3$ | 3 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | c5n.xlarge |
| PostgreSQL$^4$ | 3 | 2 vCPU, 7.5 GB memory | n1-standard-2 | m5.large |
| Sentinel$^4$ | 3 | 2 vCPU, 7.5 GB memory | n1-standard-2 | m5.large |
| Redis$^4$ | 3 | 2 vCPU, 7.5 GB memory | n1-standard-2 | m5.large |
| Infisical Core | 3 | 8 vCPU, 7.2 GB memory | n1-highcpu-8 | c5.2xlarge |
**Footnotes:**
1. External load balancer: If you wish to have multiple instances of the internal load balancer, you will need to use an external load balancer to distribute incoming traffic across multiple internal load balancers.
Using multiple internal load balancers is recommended for high-traffic environments. In the following guide we will use a single internal load balancer, as external load balancing falls outside the scope of this guide.
2. Internal load balancer: The internal load balancer (a HAProxy instance) is used to distribute incoming traffic across multiple Infisical Core instances, Postgres nodes, and Redis nodes. The internal load balancer exposes a set of ports _(80 for Infiscial, 5000 for Read/Write postgres, 5001 for Read-only postgres, and 6379 for Redis)_. Where these ports route to is determained by the internal load balancer based on the availability and health of the service nodes.
The internal load balancer is only accessible from within the same network, and is not exposed to the public internet.
3. Etcd cluster: Etcd is a distributed key-value store used to store and distribute data between the PostgreSQL nodes. Etcd is dependent on high disk I/O performance, therefore it is highly recommended to use highly performant SSD disks for the Etcd nodes, with _at least_ 80GB of disk space.
4. The Redis and PostgreSQL nodes will automatically be configured for high availability and used in your Infisical Core instances. However, you can optionally choose to bring your own database (BYOD), and skip these nodes. See more on how to [provide your own databases](#provide-your-own-databases).
<Info>
For all services that require multiple nodes, it is recommended to deploy them across multiple availability zones (AZs) to ensure high availability and fault tolerance. This will help prevent service disruptions in the event of an AZ failure.
</Info>
![High availability stack](../../images/self-hosting/deployment-options/native/ha-stack.png)
The image above shows how a high availability deployment of Infisical is structured. In this example, an external load balancer is used to distribute incoming traffic across multiple internal load balancers. The internal load balancers. The external load balancer isn't required, and it will require additional configuration to set up.
### Fault Tolerance
This setup provides N+1 redundancy, meaning it can tolerate the failure of any single node without service interruption.
## Ansible
### What is Ansible
Ansible is an open-source automation tool that simplifies application deployment, configuration management, and task automation.
At Infisical, we use Ansible to automate the deployment of Infisical services. The Ansible roles are designed to make it easy to deploy Infisical services in a high availability environment.
### Installing Ansible
<Steps>
<Step title="Install using the pipx Python package manager">
```bash
pipx install --include-deps ansible
```
</Step>
<Step title="Verify the installation">
```bash
ansible --version
```
</Step>
</Steps>
### Understanding Ansible Concepts
* Inventory _(inventory.ini)_: A file that lists your target hosts.
* Playbook _(playbook.yml)_: YAML file containing a set of tasks to be executed on hosts.
* Roles: Reusable units of organization for playbooks. Roles are used to group tasks together in a structured and reusable manner.
### Basic Ansible Commands
Running a playbook with with an invetory file:
```bash
ansible-playbook -i inventory.ini playbook.yml
```
This is how you would run the playbook containing the roles for setting up Infisical in a high availability environment.
### Installing the Infisical High Availability Deployment Ansible Role
The Infisical Ansible role is available on Ansible Galaxy. You can install the role by running the following command:
```bash
ansible-galaxy collection install infisical.infisical_core_ha_deployment
```
## Set up components
1. External load balancer (optional, and not covered in this guide)
2. [Configure Etcd cluster](#configure-etcd-cluster)
3. [Configure PostgreSQL database](#configure-postgresql-database)
4. [Configure Redis/Sentinel](#configure-redis-and-sentinel)
5. [Configure Infisical Core](#configure-infisical-core)
The servers start on the same 52.1.0.0/24 private network range, and can connect to each other freely on these addresses.
The following list includes descriptions of each server and its assigned IP:
52.1.0.1: External Load Balancer
52.1.0.2: Internal Load Balancer
52.1.0.3: Etcd 1
52.1.0.4: Etcd 2
52.1.0.5: Etcd 3
52.1.0.6: PostgreSQL 1
52.1.0.7: PostgreSQL 2
52.1.0.8: PostgreSQL 3
52.1.0.9: Redis 1
52.1.0.10: Redis 2
52.1.0.11: Redis 3
52.1.0.12: Sentinel 1
52.1.0.13: Sentinel 2
52.1.0.14: Sentinel 3
52.1.0.15: Infisical Core 1
52.1.0.16: Infisical Core 2
52.1.0.17: Infisical Core 3
### Configure Etcd cluster
Configuring the ETCD cluster is the first step in setting up a high availability deployment of Infisical.
The ETCD cluster is used to store and distribute data between the PostgreSQL nodes. The ETCD cluster is a distributed key-value store that is highly available and fault-tolerant.
```yaml example.playbook.yml
- hosts: all
gather_facts: true
- name: Set up etcd cluster
hosts: etcd
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: etcd
```
```ini example.inventory.ini
[etcd]
etcd1 ansible_host=52.1.0.3
etcd2 ansible_host=52.1.0.4
etcd3 ansible_host=52.1.0.5
[etcd:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=./ssh-key.pem
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
```
### Configure PostgreSQL database
The Postgres role takes a set of parameters that are used to configure your PostgreSQL database.
Make sure to set the following variables in your playbook.yml file:
- `postgres_super_user_password`: The password for the 'postgres' database user.
- `postgres_db_name`: The name of the database that will be created on the leader node and replicated to the secondary nodes.
- `postgres_user`: The name of the user that will be created on the leader node and replicated to the secondary nodes.
- `postgres_user_password`: The password for the user that will be created on the leader node and replicated to the secondary nodes.
- `etcd_hosts`: The list of etcd hosts that the PostgreSQL nodes will use to communicate with etcd. By default you want to keep this value set to `"{{ groups['etcd'] }}"`
```yaml example.playbook.yml
- hosts: all
gather_facts: true
- name: Set up PostgreSQL with Patroni
hosts: postgres
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: postgres
vars:
postgres_super_user_password: "your-super-user-password"
postgres_user: infisical-user
postgres_user_password: "your-password"
postgres_db_name: infisical-db
etcd_hosts: "{{ groups['etcd'] }}"
```
```ini example.inventory.ini
[postgres]
postgres1 ansible_host=52.1.0.6
postgres2 ansible_host=52.1.0.7
postgres3 ansible_host=52.1.0.8
```
### Configure Redis and Sentinel
The Redis role takes a single variable as input, which is the redis password.
The Sentinel and Redis hosts will run the same role, therefore we are running the task for both the sentinel and redis hosts, `hosts: redis:sentinel`.
- `redis_password`: The password that will be set for the Redis instance.
```yaml example.playbook.yml
- hosts: all
gather_facts: true
- name: Setup Redis and Sentinel
hosts: redis:sentinel
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: redis
vars:
redis_password: "REDIS_PASSWORD"
```
```ini example.inventory.ini
[redis]
redis1 ansible_host=52.1.0.9
redis2 ansible_host=52.1.0.10
redis3 ansible_host=52.1.0.11
[sentinel]
sentinel1 ansible_host=52.1.0.12
sentinel2 ansible_host=52.1.0.13
sentinel3 ansible_host=52.1.0.14
```
### Configure Internal Load Balancer
The internal load balancer used is HAProxy. HAProxy will expose a set of ports as listed below. Each port will route to a different service based on the availability and health of the service nodes.
- Port 80: Infisical Core
- Port 5000: Read/Write PostgreSQL
- Port 5001: Read-only PostgreSQL
- Port 6379: Redis
- Port 7000: HAProxy monitoring
These ports will need to be exposed on your network to become accessible from the outside world.
The HAProxy configuration file is generated by the Infisical Core role, and is located at `/etc/haproxy/haproxy.cfg` on your internal load balancer node.
The HAProxy setup comes with a monitoring panel. You have to set the username/password combination for the monitoring panel by setting the `stats_user` and `stats_password` variables in the HAProxy role.
Once the HAProxy role has fully executed, you can monitor your HA setup by navigating to `http://52.1.0.2:7000/haproxy?stats` in your browser.
```ini example.inventory.ini
[haproxy]
internal_lb ansible_host=52.1.0.2
```
```yaml example.playbook.yml
- name: Set up HAProxy
hosts: haproxy
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: haproxy
vars:
stats_user: "stats-username"
stats_password: "stats-password!"
postgres_servers: "{{ groups['postgres'] }}"
infisical_servers: "{{ groups['infisical'] }}"
redis_servers: "{{ groups['redis'] }}"
```
### Configure Infisical Core
The Infisical Core role will set up your actual Infisical instances.
The `env_vars` variable is used to set the environment variables that Infisical will use. The minimum required environment variables are `ENCRYPTION_KEY` and `AUTH_SECRET`. You can find a list of all available environment variables [here](/docs/self-hosting/configuration/envars#general-platform).
The `DB_CONNECTION_URI` and `REDIS_URL` variables will automatically be set if you're running the full playbook. However, you can choose to set them yourself, and skip the Postgres, etcd, redis/sentinel roles entirely.
<Info>
If you later need to add new environment varibles to your Infisical deployments, it's important you add the variables to **all** your Infisical nodes.<br/>
You can find the environment file for Infisical at `/etc/infisical/environment`.<br/>
After editing the environment file, you need to reload the Infisical service by doing `systemctl restart infisical`.
</Info>
```yaml example.playbook.yml
- hosts: all
gather_facts: true
- name: Setup Infisical
hosts: infisical
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: infisical
env_vars:
ENCRYPTION_KEY: "YOUR_ENCRYPTION_KEY" # openssl rand -hex 16
AUTH_SECRET: "YOUR_AUTH_SECRET" # openssl rand -base64 32
```
```ini example.inventory.ini
[infisical]
infisical1 ansible_host=52.1.0.15
infisical2 ansible_host=52.1.0.16
infisical3 ansible_host=52.1.0.17
```
## Provide your own databases
Bringing your own database is an option using the Infisical Core deployment role.
By bringing your own database, you're able to skip the Etcd, Postgres, and Redis/Sentinel roles entirely.
To bring your own database, you need to set the `DB_CONNECTION_URI` and `REDIS_URL` environment variables in the Infisical Core role.
```yaml example.playbook.yml
- hosts: all
gather_facts: true
- name: Setup Infisical
hosts: infisical
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: infisical
env_vars:
ENCRYPTION_KEY: "YOUR_ENCRYPTION_KEY" # openssl rand -hex 16
AUTH_SECRET: "YOUR_AUTH_SECRET" # openssl rand -base64 32
DB_CONNECTION_URI: "postgres://user:password@localhost:5432/infisical"
REDIS_URL: "redis://localhost:6379"
```
```ini example.inventory.ini
[infisical]
infisical1 ansible_host=52.1.0.15
infisical2 ansible_host=52.1.0.16
infisical3 ansible_host=52.1.0.17
```
## Full deployment example
To make it easier to get started, we've provided a full deployment example that you can use to deploy Infisical in a high availability environment.
- This deployment does not use an external load balancer.
- You **must** change the environment variables defined in the `playbook.yml` example.
- You have update the IP addresses in the `inventory.ini` file to match your own network configuration.
- You need to set the SSH key and ssh user in the `inventory.ini` file.
<Steps>
<Step title="Install Ansible">
Install Ansible using the pipx Python package manager.
```bash
pipx install --include-deps ansible
```
</Step>
<Step title="Install the Infisical deployment Ansible Role">
Install the Infisical deployment role from Ansible Galaxy.
```bash
ansible-galaxy collection install infisical.infisical_core_ha_deployment
```
</Step>
<Step title="Setup your hosts">
Create an `inventory.ini` file, and define your hosts and their IP addresses. You can use the example below as a template, and update the IP addresses to match your own network configuration.
Make sure to set the SSH key and ssh user in the `inventory.ini` file. Please see the example below.
```ini example.inventory.ini
[etcd]
etcd1 ansible_host=52.1.0.3
etcd2 ansible_host=52.1.0.4
etcd3 ansible_host=52.1.0.5
[postgres]
postgres1 ansible_host=52.1.0.6
postgres2 ansible_host=52.1.0.7
postgres3 ansible_host=52.1.0.8
[infisical]
infisical1 ansible_host=52.1.0.15
infisical2 ansible_host=52.1.0.16
infisical3 ansible_host=52.1.0.17
[redis]
redis1 ansible_host=52.1.0.9
redis2 ansible_host=52.1.0.10
redis3 ansible_host=52.1.0.11
[sentinel]
sentinel1 ansible_host=52.1.0.12
sentinel2 ansible_host=52.1.0.13
sentinel3 ansible_host=52.1.0.14
[haproxy]
internal_lb ansible_host=52.1.0.2
; This can be defined individually for each host, or globally for all hosts.
; In this case the credentials are the same for all hosts, so we define them globally as seen below ([all:vars]).
[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=./your-ssh-key.pem
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
```
</Step>
<Step title="Setup your Ansible playbook">
The Ansible playbook is where you define which roles/tasks to execute on which hosts.
```yaml example.playbook.yml
---
# Important, we must gather facts from all hosts prior to running the roles to ensure we have all the information we need.
- hosts: all
gather_facts: true
- name: Set up etcd cluster
hosts: etcd
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: etcd
- name: Set up PostgreSQL with Patroni
hosts: postgres
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: postgres
vars:
postgres_super_user_password: "<ENTER_SUPERUSER_PASSWORD>" # Password for the 'postgres' database user
# A database with these credentials will be created on the leader node, and replicated to the secondary nodes.
postgres_db_name: <ENTER_DB_NAME>
postgres_user: <ENTER_DB_USER>
postgres_user_password: <ENTER_DB_USER_PASSWORD>
etcd_hosts: "{{ groups['etcd'] }}"
- name: Setup Redis and Sentinel
hosts: redis:sentinel
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: redis
vars:
redis_password: "<ENTER_REDIS_PASSWORD>"
- name: Set up HAProxy
hosts: haproxy
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: haproxy
vars:
stats_user: "<ENTER_HAPROXY_STATS_USERNAME>"
stats_password: "<ENTER_HAPROXY_STATS_PASSWORD>"
postgres_servers: "{{ groups['postgres'] }}"
infisical_servers: "{{ groups['infisical'] }}"
redis_servers: "{{ groups['redis'] }}"
- name: Setup Infisical
hosts: infisical
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: infisical
env_vars:
ENCRYPTION_KEY: "YOUR_ENCRYPTION_KEY" # openssl rand -hex 16
AUTH_SECRET: "YOUR_AUTH_SECRET" # openssl rand -base64 32
```
</Step>
<Step title="Run the Ansible playbook">
After creating the `playbook.yml` and `inventory.ini` files, you can run the playbook using the following command
```bash
ansible-playbook -i inventory.ini playbook.yml
```
This step may take upwards of 10 minutes to complete, depending on the number of nodes and the network speed.
Once the playbook has completed, you should have a fully deployed high availability Infisical environment.
To access Infisical, you can try navigating to `http://52.1.0.2`, in order to view your newly deployed Infisical instance.
</Step>
</Steps>
## Post-deployment steps
After deploying Infisical in a high availability environment, you should perform the following post-deployment steps:
- Check your deployment to ensure that all services are running as expected. You can use the HAProxy monitoring panel to check the status of your services (http://52.1.0.2:7000/haproxy?stats)
- Attempt to access the Infisical Core instances to ensure that they are accessible from the internal load balancer. (http://52.1.0.2)
A HAProxy stats page indicating success will look like the image below
![HAProxy stats page](../../images/self-hosting/deployment-options/native/haproxy-stats.png)
## Security Considerations
### Network Security
Secure the network that your instances run on. While this falls outside the scope of Infisical deployment, it's crucial for overall security.
AWS-specific recommendations:
Use Virtual Private Cloud (VPC) to isolate your infrastructure.
Configure security groups to restrict inbound and outbound traffic.
Use Network Access Control Lists (NACLs) for additional network-level security.
<Note>
Please take note that the Infisical team cannot provide infrastructure support for **free self-hosted** deployments.<br/>If you need help with infrastructure, we recommend upgrading to a [paid plan](https://infisical.com/pricing) which includes infrastructure support.
You can also join our community [Slack](https://infisical.com/slack) for help and support from the community.
</Note>
### Troubleshooting
<Accordion title="Ansible: Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user">
If you encounter this issue, please update your ansible config (`ansible.cfg`) file with the following configuration:
```ini
[defaults]
allow_world_readable_tmpfiles = true
```
You can read more about the solution [here](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/sh_shell.html#parameter-world_readable_temp)
</Accordion>
<Accordion title="I'm unable to connect to access the Infisical instance on the web">
This issue can be caused by a number of reasons, mostly realted to the network configuration. Here are a few things you can check:
1. Ensure that the firewall is not blocking the connection. You can check this by running `ufw status`. Ensure that port 80 is open.
2. If you're using a cloud provider like AWS or GCP, ensure that the security group allows traffic on port 80.
3. Ensure that the HAProxy service is running. You can check this by running `systemctl status haproxy`.
4. Ensure that the Infisical service is running. You can check this by running `systemctl status infisical`.
</Accordion>

@ -1,5 +1,5 @@
---
title: "AWS ECS"
title: "AWS ECS (HA)"
description: "Reference architecture for self-hosting Infisical on AWS ECS"
---

@ -0,0 +1,383 @@
---
title: "Linux (HA)"
description: "Infisical High Availability Deployment architecture for Linux"
---
This guide describes how to achieve a highly available deployment of Infisical on Linux machines without containerization. The architecture provided serves as a foundation for minimum high availability, which you can scale based on your specific requirements.
## Architecture Overview
![High availability stack](/images/self-hosting/deployment-options/native/ha-stack.png)
The deployment consists of the following key components:
| Service | Nodes | Recommended Specs | GCP Instance | AWS Instance |
|---------------------------|-------|---------------------------|-----------------|--------------|
| External Load Balancer | 1 | 4 vCPU, 4 GB memory | n1-highcpu-4 | c5n.xlarge |
| Internal Load Balancer | 1 | 4 vCPU, 4 GB memory | n1-highcpu-4 | c5n.xlarge |
| Etcd Cluster | 3 | 4 vCPU, 4 GB memory | n1-highcpu-4 | c5n.xlarge |
| PostgreSQL Cluster | 3 | 2 vCPU, 8 GB memory | n1-standard-2 | m5.large |
| Redis + Sentinel | 3+3 | 2 vCPU, 8 GB memory | n1-standard-2 | m5.large |
| Infisical Core | 3 | 2 vCPU, 4 GB memory | n1-highcpu-2 | c5.large |
### Network Architecture
All servers operate within the 52.1.0.0/24 private network range with the following IP assignments:
| Service | IP Address |
|----------------------|------------|
| External Load Balancer| 52.1.0.1 |
| Internal Load Balancer| 52.1.0.2 |
| Etcd Node 1 | 52.1.0.3 |
| Etcd Node 2 | 52.1.0.4 |
| Etcd Node 3 | 52.1.0.5 |
| PostgreSQL Node 1 | 52.1.0.6 |
| PostgreSQL Node 2 | 52.1.0.7 |
| PostgreSQL Node 3 | 52.1.0.8 |
| Redis Node 1 | 52.1.0.9 |
| Redis Node 2 | 52.1.0.10 |
| Redis Node 3 | 52.1.0.11 |
| Sentinel Node 1 | 52.1.0.12 |
| Sentinel Node 2 | 52.1.0.13 |
| Sentinel Node 3 | 52.1.0.14 |
| Infisical Core 1 | 52.1.0.15 |
| Infisical Core 2 | 52.1.0.16 |
| Infisical Core 3 | 52.1.0.17 |
## Component Setup Guide
### 1. Configure Etcd Cluster
The Etcd cluster is needed for leader election in the PostgreSQL HA setup. Skip this step if using managed PostgreSQL.
1. Install Etcd on each node:
```bash
sudo apt update
sudo apt install etcd
```
2. Configure each node with unique identifiers and cluster membership. Example configuration for Node 1 (`/etc/etcd/etcd.conf`):
```yaml
name: etcd1
data-dir: /var/lib/etcd
initial-cluster-state: new
initial-cluster-token: etcd-cluster-1
initial-cluster: etcd1=http://52.1.0.3:2380,etcd2=http://52.1.0.4:2380,etcd3=http://52.1.0.5:2380
initial-advertise-peer-urls: http://52.1.0.3:2380
listen-peer-urls: http://52.1.0.3:2380
listen-client-urls: http://52.1.0.3:2379,http://127.0.0.1:2379
advertise-client-urls: http://52.1.0.3:2379
```
### 2. Configure PostgreSQL
For production deployments, you have two options for highly available PostgreSQL:
#### Option A: Managed PostgreSQL Service (Recommended for Most Users)
Use cloud provider managed services:
- AWS: Amazon RDS for PostgreSQL with Multi-AZ
- GCP: Cloud SQL for PostgreSQL with HA configuration
- Azure: Azure Database for PostgreSQL with zone redundant HA
These services handle replication, failover, and maintenance automatically.
#### Option B: Self-Managed PostgreSQL Cluster
Full HA installation guide of PostgreSQL is beyond the scope of this document. However, we have provided an overview of resources and code snippets below to guide your deployment.
1. Required Components:
- PostgreSQL 14+ on each node
- Patroni for cluster management
- Etcd for distributed consensus
2. Documentation we recommend you read:
- [Complete Patroni Setup Guide](https://patroni.readthedocs.io/en/latest/README.html)
- [PostgreSQL Replication Documentation](https://www.postgresql.org/docs/current/high-availability.html)
3. Key Steps Overview:
```bash
# 1. Install requirements on each PostgreSQL node
sudo apt update
sudo apt install -y postgresql-14 postgresql-contrib-14 python3-pip
pip3 install patroni[etcd] psycopg2-binary
# 2. Create Patroni config directory
sudo mkdir /etc/patroni
sudo chown postgres:postgres /etc/patroni
# 3. Create Patroni configuration (example for first node)
# /etc/patroni/config.yml - REQUIRES CAREFUL CUSTOMIZATION
```
```yaml
scope: infisical-cluster
namespace: /db/
name: postgresql1
restapi:
listen: 52.1.0.6:8008
connect_address: 52.1.0.6:8008
etcd:
hosts: 52.1.0.3:2379,52.1.0.4:2379,52.1.0.5:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
parameters:
max_connections: 1000
shared_buffers: 2GB
work_mem: 8MB
max_worker_processes: 8
max_parallel_workers_per_gather: 4
max_parallel_workers: 8
wal_level: replica
hot_standby: "on"
max_wal_senders: 10
max_replication_slots: 10
hot_standby_feedback: "on"
```
4. Important considerations:
- Proper disk configuration for WAL and data directories
- Network latency between nodes
- Backup strategy and point-in-time recovery
- Monitoring and alerting setup
- Connection pooling configuration
- Security and network access controls
5. Recommended readings:
- [PostgreSQL Backup and Recovery](https://www.postgresql.org/docs/current/backup.html)
- [PostgreSQL Monitoring](https://www.postgresql.org/docs/current/monitoring.html)
### 3. Configure Redis and Sentinel
Similar to PostgreSQL, a full HA Redis setup guide is beyond the scope of this document. Below are the key resources and considerations for your deployment.
#### Option A: Managed Redis Service (Recommended for Most Users)
Use cloud provider managed Redis services:
- AWS: ElastiCache for Redis with Multi-AZ
- GCP: Memorystore for Redis with HA
- Azure: Azure Cache for Redis with zone redundancy
Follow your cloud provider's documentation:
- [AWS ElastiCache Documentation](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/WhatIs.html)
- [GCP Memorystore Documentation](https://cloud.google.com/memorystore/docs/redis)
- [Azure Redis Cache Documentation](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/)
#### Option B: Self-Managed Redis Cluster
Setting up a production Redis HA cluster requires understanding several components. Refer to these linked resources:
1. Required Reading:
- [Redis Sentinel Documentation](https://redis.io/docs/management/sentinel/)
- [Redis Replication Guide](https://redis.io/topics/replication)
- [Redis Security Guide](https://redis.io/topics/security)
2. Key Steps Overview:
```bash
# 1. Install Redis on all nodes
sudo apt update
sudo apt install redis-server
# 2. Configure master node (52.1.0.9)
# /etc/redis/redis.conf
```
```conf
bind 52.1.0.9
port 6379
dir /var/lib/redis
maxmemory 3gb
maxmemory-policy noeviction
requirepass "your_redis_password"
masterauth "your_redis_password"
```
3. Configure replica nodes (`52.1.0.10`, `52.1.0.11`):
```conf
bind 52.1.0.10 # Change for each replica
port 6379
dir /var/lib/redis
replicaof 52.1.0.9 6379
masterauth "your_redis_password"
requirepass "your_redis_password"
```
4. Configure Sentinel nodes (`52.1.0.12`, `52.1.0.13`, `52.1.0.14`):
```conf
port 26379
sentinel monitor mymaster 52.1.0.9 6379 2
sentinel auth-pass mymaster "your_redis_password"
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
```
5. Recommended Additional Reading:
- [Redis High Availability Tools](https://redis.io/topics/high-availability)
- [Redis Sentinel Client Implementation](https://redis.io/topics/sentinel-clients)
### 4. Configure HAProxy Load Balancer
Install and configure HAProxy for internal load balancing:
```conf ha-proxy-config
global
maxconn 10000
log stdout format raw local0
defaults
log global
mode tcp
retries 3
timeout client 30m
timeout connect 10s
timeout server 30m
timeout check 5s
listen stats
mode http
bind *:7000
stats enable
stats uri /
resolvers hostdns
nameserver dns 127.0.0.11:53
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold valid 5s
frontend postgres_master
bind *:5000
default_backend postgres_master_backend
frontend postgres_replicas
bind *:5001
default_backend postgres_replica_backend
backend postgres_master_backend
option httpchk GET /master
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server postgres-1 52.1.0.6:5432 check port 8008
server postgres-2 52.1.0.7:5432 check port 8008
server postgres-3 52.1.0.8:5432 check port 8008
backend postgres_replica_backend
option httpchk GET /replica
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server postgres-1 52.1.0.6:5432 check port 8008
server postgres-2 52.1.0.7:5432 check port 8008
server postgres-3 52.1.0.8:5432 check port 8008
frontend redis_master_frontend
bind *:6379
default_backend redis_master_backend
backend redis_master_backend
option tcp-check
tcp-check send AUTH\ 123456\r\n
tcp-check expect string +OK
tcp-check send PING\r\n
tcp-check expect string +PONG
tcp-check send info\ replication\r\n
tcp-check expect string role:master
tcp-check send QUIT\r\n
tcp-check expect string +OK
server redis-1 52.1.0.9:6379 check inter 1s
server redis-2 52.1.0.10:6379 check inter 1s
server redis-3 52.1.0.11:6379 check inter 1s
frontend infisical_frontend
bind *:80
default_backend infisical_backend
backend infisical_backend
option httpchk GET /api/status
http-check expect status 200
server infisical-1 52.1.0.15:8080 check inter 1s
server infisical-2 52.1.0.16:8080 check inter 1s
server infisical-3 52.1.0.17:8080 check inter 1s
```
### 5. Deploy Infisical Core
<Tabs>
<Tab title="Debian/Ubuntu">
First, add the Infisical repository:
```bash
curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-core/setup.deb.sh' \
| sudo -E bash
```
Then install Infisical:
```bash
sudo apt-get update && sudo apt-get install -y infisical-core
```
<Info>
For production environments, we strongly recommend installing a specific version of the package to maintain consistency across reinstalls. View available versions at [Infisical Package Versions](https://cloudsmith.io/~infisical/repos/infisical-core/packages/).
</Info>
</Tab>
<Tab title="RedHat/CentOS/Amazon Linux">
First, add the Infisical repository:
```bash
curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-core/setup.rpm.sh' \
| sudo -E bash
```
Then install Infisical:
```bash
sudo yum install infisical-core
```
<Info>
For production environments, we strongly recommend installing a specific version of the package to maintain consistency across reinstalls. View available versions at [Infisical Package Versions](https://cloudsmith.io/~infisical/repos/infisical-core/packages/).
</Info>
</Tab>
</Tabs>
Next, create configuration file `/etc/infisical/infisical.rb` with the following:
```ruby
infisical_core['ENCRYPTION_KEY'] = 'your-secure-encryption-key'
infisical_core['AUTH_SECRET'] = 'your-secure-auth-secret'
infisical_core['DB_CONNECTION_URI'] = 'postgres://user:pass@52.1.0.2:5000/infisical'
infisical_core['REDIS_URL'] = 'redis://52.1.0.2:6379'
infisical_core['PORT'] = 8080
```
To generate `ENCRYPTION_KEY` and `AUTH_SECRET` view the [following configurations documentation here](/self-hosting/configuration/envars).
If you are using managed services for either Postgres or Redis, please replace the values of the secrets accordingly.
Lastly, start and verify each node running infisical-core:
```bash
sudo infisical-ctl reconfigure
sudo infisical-ctl status
```
## Monitoring and Maintenance
1. Monitor HAProxy stats: `http://52.1.0.2:7000/haproxy?stats`
2. Monitor Infisical logs: `sudo infisical-ctl tail`
3. Check cluster health:
- Etcd: `etcdctl cluster-health`
- PostgreSQL: `patronictl list`
- Redis: `redis-cli info replication`

31
npm/package-lock.json generated

@ -9,12 +9,22 @@
"version": "0.0.0",
"hasInstallScript": true,
"dependencies": {
"tar": "^6.2.0"
"tar": "^6.2.0",
"yauzl": "^3.2.0"
},
"bin": {
"infisical": "bin/infisical"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
@ -87,6 +97,12 @@
"node": ">=10"
}
},
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
"license": "MIT"
},
"node_modules/tar": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
@ -107,6 +123,19 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yauzl": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz",
"integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==",
"license": "MIT",
"dependencies": {
"buffer-crc32": "~0.2.3",
"pend": "~1.2.0"
},
"engines": {
"node": ">=12"
}
}
}
}

@ -8,7 +8,7 @@
"command-line"
],
"bin": {
"infisical": "bin/infisical"
"infisical": "./bin/infisical"
},
"repository": {
"type": "git",
@ -16,9 +16,10 @@
},
"author": "Infisical Inc, <daniel@infisical.com>",
"scripts": {
"postinstall": "node src/index.cjs"
"preinstall": "node src/index.cjs"
},
"dependencies": {
"tar": "^6.2.0"
"tar": "^6.2.0",
"yauzl": "^3.2.0"
}
}

@ -4,13 +4,20 @@ const stream = require("node:stream");
const tar = require("tar");
const path = require("path");
const zlib = require("zlib");
const yauzl = require("yauzl");
const packageJSON = require("../package.json");
const supportedPlatforms = ["linux", "darwin", "win32", "freebsd"];
const supportedPlatforms = ["linux", "darwin", "win32", "freebsd", "windows"];
const outputDir = "bin";
const getPlatform = () => {
const platform = process.platform;
let platform = process.platform;
if (platform === "win32") {
platform = "windows";
}
if (!supportedPlatforms.includes(platform)) {
console.error("Your platform doesn't seem to be of type darwin, linux or windows");
process.exit(1);
@ -53,12 +60,55 @@ const getArchitecture = () => {
return arch;
};
async function extractZip(buffer, targetPath) {
return new Promise((resolve, reject) => {
yauzl.fromBuffer(buffer, { lazyEntries: true }, (err, zipfile) => {
if (err) return reject(err);
zipfile.readEntry();
zipfile.on("entry", entry => {
const isExecutable = entry.fileName === "infisical" || entry.fileName === "infisical.exe";
if (/\/$/.test(entry.fileName) || !isExecutable) {
// Directory entry
zipfile.readEntry();
} else {
// File entry
zipfile.openReadStream(entry, (err, readStream) => {
if (err) return reject(err);
let fileName = entry.fileName;
if (entry.fileName.endsWith(".exe")) {
fileName = "infisical.exe";
} else if (entry.fileName.includes("infisical")) {
fileName = "infisical";
}
const outputPath = path.join(targetPath, fileName);
const writeStream = fs.createWriteStream(outputPath);
readStream.pipe(writeStream);
writeStream.on("close", () => {
zipfile.readEntry();
});
});
}
});
zipfile.on("end", resolve);
zipfile.on("error", reject);
});
});
}
async function main() {
const PLATFORM = getPlatform();
const ARCH = getArchitecture();
const NUMERIC_RELEASE_VERSION = packageJSON.version;
const LATEST_RELEASE_VERSION = `v${NUMERIC_RELEASE_VERSION}`;
const downloadLink = `https://github.com/Infisical/infisical/releases/download/infisical-cli/${LATEST_RELEASE_VERSION}/infisical_${NUMERIC_RELEASE_VERSION}_${PLATFORM}_${ARCH}.tar.gz`;
const EXTENSION = PLATFORM === "windows" ? "zip" : "tar.gz";
const downloadLink = `https://github.com/Infisical/infisical/releases/download/infisical-cli/${LATEST_RELEASE_VERSION}/infisical_${NUMERIC_RELEASE_VERSION}_${PLATFORM}_${ARCH}.${EXTENSION}`;
// Ensure the output directory exists
if (!fs.existsSync(outputDir)) {
@ -77,22 +127,35 @@ async function main() {
throw new Error(`Failed to fetch: ${response.status} - ${response.statusText}`);
}
await new Promise((resolve, reject) => {
const outStream = stream.Readable.fromWeb(response.body)
.pipe(zlib.createGunzip())
.pipe(
tar.x({
C: path.join(outputDir),
filter: path => path === "infisical"
})
);
if (EXTENSION === "zip") {
// For ZIP files, we need to buffer the whole thing first
const buffer = await response.arrayBuffer();
await extractZip(Buffer.from(buffer), outputDir);
} else {
// For tar.gz files, we stream
await new Promise((resolve, reject) => {
const outStream = stream.Readable.fromWeb(response.body)
.pipe(zlib.createGunzip())
.pipe(
tar.x({
C: path.join(outputDir),
filter: path => path === "infisical"
})
);
outStream.on("error", reject);
outStream.on("close", resolve);
});
outStream.on("error", reject);
outStream.on("close", resolve);
});
}
// Give the binary execute permissions if we're not on Windows
if (PLATFORM !== "win32") {
// Platform-specific tasks
if (PLATFORM === "windows") {
// We create an empty file called 'infisical'. This file has no functionality, except allowing NPM to correctly create the symlink.
// Reason why this doesn't work without the empty file, is because the files downloaded are a .ps1, .exe, and .cmd file. None of these match the binary name from the package.json['bin'] field.
// This is a bit hacky, but it assures that the symlink is correctly created.
fs.closeSync(fs.openSync(path.join(outputDir, "infisical"), "w"));
} else {
// Unix systems only need chmod
fs.chmodSync(path.join(outputDir, "infisical"), "755");
}
} catch (error) {