mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-19 21:57:10 +00:00
Compare commits
45 Commits
ecs-docs
...
allow-name
Author | SHA1 | Date | |
---|---|---|---|
fe99c12c0d | |||
332b0e2cc3 | |||
8bc9a5fed6 | |||
55e75bbbef | |||
61ff732ec0 | |||
609b224ca9 | |||
c23e16105b | |||
c10f4ece51 | |||
bcdb1b11bc | |||
01d850f7e8 | |||
2d1b60a520 | |||
8de2302d98 | |||
0529b50ad7 | |||
c74fe0ca73 | |||
d5f8526a84 | |||
782ae7a41d | |||
d355956daf | |||
5b9c0438a2 | |||
11399d73dc | |||
38ed39c2f8 | |||
4e3827780f | |||
644cdf5a67 | |||
0d6ea0d69e | |||
237979a1c6 | |||
4a566cf83f | |||
654b8ab5ca | |||
ac0780266b | |||
7a253ddcc7 | |||
b65677a708 | |||
c1eb97ee53 | |||
937e48dbc5 | |||
b3d4787e21 | |||
72d46efba5 | |||
b6eb08167f | |||
582472e4cc | |||
3b3b76548b | |||
f8416ad891 | |||
31e49672d5 | |||
9248bdf463 | |||
87c061ae9b | |||
e9fa631c8f | |||
cff15b64c4 | |||
136f5a6052 | |||
7e8f9ec9e4 | |||
202efce10d |
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
@ -1,6 +1,6 @@
|
||||
# Description 📣
|
||||
|
||||
<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. -->
|
||||
<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. Here's how we expect a pull request to be : https://infisical.com/docs/contributing/getting-started/pull-requests -->
|
||||
|
||||
## Type ✨
|
||||
|
||||
@ -19,4 +19,6 @@
|
||||
|
||||
---
|
||||
|
||||
- [ ] I have read the [contributing guide](https://infisical.com/docs/contributing/overview), agreed and acknowledged the [code of conduct](https://infisical.com/docs/contributing/code-of-conduct). 📝
|
||||
- [ ] I have read the [contributing guide](https://infisical.com/docs/contributing/getting-started/overview), agreed and acknowledged the [code of conduct](https://infisical.com/docs/contributing/getting-started/code-of-conduct). 📝
|
||||
|
||||
<!-- If you have any questions regarding contribution, here's the FAQ : https://infisical.com/docs/contributing/getting-started/faq -->
|
8
.github/values.yaml
vendored
8
.github/values.yaml
vendored
@ -19,11 +19,11 @@ infisical:
|
||||
## @param backend.name Backend name
|
||||
##
|
||||
name: infisical
|
||||
replicaCount: 2
|
||||
replicaCount: 3
|
||||
image:
|
||||
repository: infisical/infisical
|
||||
tag: "latest-postgres"
|
||||
pullPolicy: IfNotPresent
|
||||
repository: infisical/staging_infisical
|
||||
tag: "latest"
|
||||
pullPolicy: Always
|
||||
|
||||
deploymentAnnotations:
|
||||
secrets.infisical.com/auto-reload: "true"
|
||||
|
@ -104,7 +104,6 @@ ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=backend-runner /app /backend
|
||||
COPY --from=backend-runner /app/dist/services/smtp/templates /backend/dist/templates
|
||||
|
||||
COPY --from=frontend-runner /app ./backend/frontend-build
|
||||
|
||||
|
30
README.md
30
README.md
@ -33,7 +33,7 @@
|
||||
<img src="https://img.shields.io/github/commit-activity/m/infisical/infisical" alt="git commit activity" />
|
||||
</a>
|
||||
<a href="https://cloudsmith.io/~infisical/repos/">
|
||||
<img src="https://img.shields.io/badge/Downloads-2.58M-orange" alt="Cloudsmith downloads" />
|
||||
<img src="https://img.shields.io/badge/Downloads-6.95M-orange" alt="Cloudsmith downloads" />
|
||||
</a>
|
||||
<a href="https://infisical.com/slack">
|
||||
<img src="https://img.shields.io/badge/chat-on%20Slack-blueviolet" alt="Slack community channel" />
|
||||
@ -53,17 +53,19 @@ We're on a mission to make secret management more accessible to everyone, not ju
|
||||
|
||||
## Features
|
||||
|
||||
- **[User-friendly dashboard](https://infisical.com/docs/documentation/platform/project)** to manage secrets across projects and environments (e.g. development, production, etc.)
|
||||
- **[Client SDKs](https://infisical.com/docs/sdks/overview)** to fetch secrets for your apps and infrastructure on demand
|
||||
- **[Infisical CLI](https://infisical.com/docs/cli/overview)** to fetch and inject secrets into any framework in local development
|
||||
- **[Native integrations](https://infisical.com/docs/integrations/overview)** with platforms like GitHub, Vercel, Netlify, and more
|
||||
- [**Automatic Kubernetes deployment secret reloads**](https://infisical.com/docs/documentation/getting-started/kubernetes)
|
||||
- **[Complete control over your data](https://infisical.com/docs/self-hosting/overview)** - host it yourself on any infrastructure
|
||||
- **[Secret versioning](https://infisical.com/docs/documentation/platform/secret-versioning)** and **[Point-in-Time Recovery]()** to version every secret and project state
|
||||
- **[Audit logs](https://infisical.com/docs/documentation/platform/audit-logs)** to record every action taken in a project
|
||||
- **Role-based Access Controls** per environment
|
||||
- [**Simple on-premise deployments** to AWS, Digital Ocean, and more](https://infisical.com/docs/self-hosting/overview)
|
||||
- [**Secret Scanning and Leak Prevention**](https://infisical.com/docs/cli/scanning-overview)
|
||||
- **[User-friendly dashboard](https://infisical.com/docs/documentation/platform/project)** to manage secrets across projects and environments (e.g. development, production, etc.).
|
||||
- **[Client SDKs](https://infisical.com/docs/sdks/overview)** to fetch secrets for your apps and infrastructure on demand.
|
||||
- **[Infisical CLI](https://infisical.com/docs/cli/overview)** to fetch and inject secrets into any framework in local development and CI/CD.
|
||||
- **[Infisical API](https://infisical.com/docs/api-reference/overview/introduction)** to perform CRUD operation on secrets, users, projects, and any other resource in Infisical.
|
||||
- **[Native integrations](https://infisical.com/docs/integrations/overview)** with platforms like [GitHub](https://infisical.com/docs/integrations/cicd/githubactions), [Vercel](https://infisical.com/docs/integrations/cloud/vercel), [AWS](https://infisical.com/docs/integrations/cloud/aws-secret-manager), and tools like [Terraform](https://infisical.com/docs/integrations/frameworks/terraform), [Ansible](https://infisical.com/docs/integrations/platforms/ansible), and more.
|
||||
- **[Infisical Kubernetes operator](https://infisical.com/docs/documentation/getting-started/kubernetes)** to managed secrets in k8s, automatically reload deployments, and more.
|
||||
- **[Infisical Agent](https://infisical.com/docs/infisical-agent/overview)** to inject secrets into your applications without modifying any code logic.
|
||||
- **[Self-hosting and on-prem](https://infisical.com/docs/self-hosting/overview)** to get complete control over your data.
|
||||
- **[Secret versioning](https://infisical.com/docs/documentation/platform/secret-versioning)** and **[Point-in-Time Recovery](https://infisical.com/docs/documentation/platform/pit-recovery)** to version every secret and project state.
|
||||
- **[Audit logs](https://infisical.com/docs/documentation/platform/audit-logs)** to record every action taken in a project.
|
||||
- **[Role-based Access Controls](https://infisical.com/docs/documentation/platform/role-based-access-controls)** to create permission sets on any resource in Infisica and assign those to user or machine identities.
|
||||
- **[Simple on-premise deployments](https://infisical.com/docs/self-hosting/overview)** to AWS, Digital Ocean, and more.
|
||||
- **[Secret Scanning and Leak Prevention](https://infisical.com/docs/cli/scanning-overview)** to prevent secrets from leaking to git.
|
||||
|
||||
And much more.
|
||||
|
||||
@ -115,9 +117,9 @@ Lean about Infisical's code scanning feature [here](https://infisical.com/docs/c
|
||||
|
||||
This repo available under the [MIT expat license](https://github.com/Infisical/infisical/blob/main/LICENSE), with the exception of the `ee` directory which will contain premium enterprise features requiring a Infisical license.
|
||||
|
||||
If you are interested in managed Infisical Cloud of self-hosted Enterprise Offering, take a look at [our website](https://infisical.com/) or [book a meeting with us](https://cal.com/vmatsiiako/infisical-demo):
|
||||
If you are interested in managed Infisical Cloud of self-hosted Enterprise Offering, take a look at [our website](https://infisical.com/) or [book a meeting with us](https://infisical.cal.com/vlad/infisical-demo):
|
||||
|
||||
<a href="https://cal.com/vmatsiiako/infisical-demo"><img alt="Schedule a meeting" src="https://cal.com/book-with-cal-dark.svg" /></a>
|
||||
<a href="[https://infisical.cal.com/vlad/infisical-demo](https://infisical.cal.com/vlad/infisical-demo)"><img alt="Schedule a meeting" src="https://cal.com/book-with-cal-dark.svg" /></a>
|
||||
|
||||
## Security
|
||||
|
||||
|
621
backend/package-lock.json
generated
621
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,12 +2,12 @@
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "./dist/main.mjs",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "tsx watch --clear-screen=false ./src/main.ts | pino-pretty --colorize --colorizeObjects --singleLine",
|
||||
"dev:docker": "nodemon",
|
||||
"build": "rimraf dist && tsup && cp -R ./src/lib/validator/disposable_emails.txt ./dist && cp -R ./src/services/smtp/templates ./dist",
|
||||
"build": "tsup",
|
||||
"start": "node dist/main.mjs",
|
||||
"type:check": "tsc --noEmit",
|
||||
"lint:fix": "eslint --fix --ext js,ts ./src",
|
||||
@ -44,7 +44,13 @@
|
||||
"@types/pg": "^8.10.9",
|
||||
"@types/picomatch": "^2.3.3",
|
||||
"@types/prompt-sync": "^4.2.3",
|
||||
"@types/resolve": "^1.20.6",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||
"@typescript-eslint/parser": "^6.20.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
@ -55,6 +61,7 @@
|
||||
"prompt-sync": "^4.2.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsc-alias": "^1.8.8",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsup": "^8.0.1",
|
||||
"tsx": "^4.4.0",
|
||||
@ -67,6 +74,7 @@
|
||||
"@casl/ability": "^6.5.0",
|
||||
"@fastify/cookie": "^9.2.0",
|
||||
"@fastify/cors": "^8.4.1",
|
||||
"@fastify/etag": "^5.1.0",
|
||||
"@fastify/formbody": "^7.4.0",
|
||||
"@fastify/helmet": "^11.1.1",
|
||||
"@fastify/passport": "^2.4.0",
|
||||
@ -79,8 +87,6 @@
|
||||
"@octokit/webhooks-types": "^7.3.1",
|
||||
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
||||
"@sindresorhus/slugify": "^2.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||
"@typescript-eslint/parser": "^6.20.0",
|
||||
"@ucast/mongo2js": "^1.3.4",
|
||||
"ajv": "^8.12.0",
|
||||
"argon2": "^0.31.2",
|
||||
@ -90,9 +96,6 @@
|
||||
"bcrypt": "^5.1.1",
|
||||
"bullmq": "^5.1.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||
"fastify": "^4.24.3",
|
||||
"fastify-plugin": "^4.5.1",
|
||||
"handlebars": "^4.7.8",
|
||||
@ -107,7 +110,6 @@
|
||||
"nanoid": "^5.0.4",
|
||||
"node-cache": "^5.1.2",
|
||||
"nodemailer": "^6.9.7",
|
||||
"ora": "^7.0.1",
|
||||
"passport-github": "^1.1.0",
|
||||
"passport-gitlab2": "^5.0.0",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
|
@ -1,10 +1,18 @@
|
||||
import knex from "knex";
|
||||
|
||||
export type TDbClient = ReturnType<typeof initDbConnection>;
|
||||
export const initDbConnection = (dbConnectionUri: string) => {
|
||||
export const initDbConnection = ({ dbConnectionUri, dbRootCert }: { dbConnectionUri: string; dbRootCert?: string }) => {
|
||||
const db = knex({
|
||||
client: "pg",
|
||||
connection: dbConnectionUri
|
||||
connection: {
|
||||
connectionString: dbConnectionUri,
|
||||
ssl: dbRootCert
|
||||
? {
|
||||
rejectUnauthorized: true,
|
||||
ca: Buffer.from(dbRootCert, "base64").toString("ascii")
|
||||
}
|
||||
: false
|
||||
}
|
||||
});
|
||||
|
||||
return db;
|
||||
|
@ -79,7 +79,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
// eslint-disable-next-line
|
||||
async (req, profile, cb) => {
|
||||
try {
|
||||
const serverCfg = getServerCfg();
|
||||
const serverCfg = await getServerCfg();
|
||||
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
||||
const { firstName } = profile;
|
||||
const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
|
||||
|
@ -38,7 +38,8 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
||||
})
|
||||
)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
.offset(offset)
|
||||
.orderBy("createdAt", "desc");
|
||||
if (startDate) {
|
||||
void sqlQuery.where("createdAt", ">=", startDate);
|
||||
}
|
||||
|
@ -15,9 +15,11 @@ const envSchema = z
|
||||
PORT: z.coerce.number().default(4000),
|
||||
REDIS_URL: zpStr(z.string()),
|
||||
HOST: zpStr(z.string().default("localhost")),
|
||||
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database conntection string")),
|
||||
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")),
|
||||
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
|
||||
NODE_ENV: z.enum(["development", "test", "production"]).default("production"),
|
||||
SALT_ROUNDS: z.coerce.number().default(10),
|
||||
INITIAL_ORGANIZATION_NAME: zpStr(z.string().optional()),
|
||||
// TODO(akhilmhdh): will be changed to one
|
||||
ENCRYPTION_KEY: zpStr(z.string().optional()),
|
||||
ROOT_ENCRYPTION_KEY: zpStr(z.string().optional()),
|
||||
|
@ -12,7 +12,11 @@ dotenv.config();
|
||||
const run = async () => {
|
||||
const logger = await initLogger();
|
||||
const appCfg = initEnvConfig(logger);
|
||||
const db = initDbConnection(appCfg.DB_CONNECTION_URI);
|
||||
const db = initDbConnection({
|
||||
dbConnectionUri: appCfg.DB_CONNECTION_URI,
|
||||
dbRootCert: appCfg.DB_ROOT_CERT
|
||||
});
|
||||
|
||||
const smtp = smtpServiceFactory(formatSmtpConfig());
|
||||
const queue = queueServiceFactory(appCfg.REDIS_URL);
|
||||
|
||||
@ -36,7 +40,7 @@ const run = async () => {
|
||||
port: appCfg.PORT,
|
||||
host: appCfg.HOST,
|
||||
listenTextResolver: (address) => {
|
||||
bootstrap();
|
||||
void bootstrap();
|
||||
return address;
|
||||
}
|
||||
});
|
||||
|
@ -5,6 +5,7 @@ import type { FastifyCookieOptions } from "@fastify/cookie";
|
||||
import cookie from "@fastify/cookie";
|
||||
import type { FastifyCorsOptions } from "@fastify/cors";
|
||||
import cors from "@fastify/cors";
|
||||
import fastifyEtag from "@fastify/etag";
|
||||
import fastifyFormBody from "@fastify/formbody";
|
||||
import helmet from "@fastify/helmet";
|
||||
import type { FastifyRateLimitOptions } from "@fastify/rate-limit";
|
||||
@ -13,11 +14,10 @@ import fasitfy from "fastify";
|
||||
import { Knex } from "knex";
|
||||
import { Logger } from "pino";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { TQueueServiceFactory } from "@app/queue";
|
||||
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
|
||||
import { getConfig } from "@lib/config/env";
|
||||
|
||||
import { globalRateLimiterCfg } from "./config/rateLimiter";
|
||||
import { fastifyErrHandler } from "./plugins/error-handler";
|
||||
import { registerExternalNextjs } from "./plugins/external-nextjs";
|
||||
@ -39,6 +39,7 @@ export const main = async ({ db, smtp, logger, queue }: TMain) => {
|
||||
const server = fasitfy({
|
||||
logger,
|
||||
trustProxy: true,
|
||||
connectionTimeout: 30 * 1000,
|
||||
ignoreTrailingSlash: true
|
||||
}).withTypeProvider<ZodTypeProvider>();
|
||||
|
||||
@ -50,6 +51,8 @@ export const main = async ({ db, smtp, logger, queue }: TMain) => {
|
||||
secret: appCfg.COOKIE_SECRET_SIGN_KEY
|
||||
});
|
||||
|
||||
await server.register(fastifyEtag);
|
||||
|
||||
await server.register<FastifyCorsOptions>(cors, {
|
||||
credentials: true,
|
||||
origin: appCfg.SITE_URL || true
|
||||
@ -72,7 +75,7 @@ export const main = async ({ db, smtp, logger, queue }: TMain) => {
|
||||
if (appCfg.isProductionMode) {
|
||||
await server.register(registerExternalNextjs, {
|
||||
standaloneMode: appCfg.STANDALONE_MODE,
|
||||
dir: path.join(__dirname, "../"),
|
||||
dir: path.join(__dirname, "../../"),
|
||||
port: appCfg.PORT
|
||||
});
|
||||
}
|
||||
|
@ -12,9 +12,9 @@ type BootstrapOpt = {
|
||||
db: Knex;
|
||||
};
|
||||
|
||||
const bootstrapCb = () => {
|
||||
const bootstrapCb = async () => {
|
||||
const appCfg = getConfig();
|
||||
const serverCfg = getServerCfg();
|
||||
const serverCfg = await getServerCfg();
|
||||
if (!serverCfg.initialized) {
|
||||
console.info(`Welcome to Infisical
|
||||
|
||||
|
@ -45,6 +45,9 @@ export const registerExternalNextjs = async (
|
||||
server.route({
|
||||
method: ["GET", "PUT", "PATCH", "POST", "DELETE"],
|
||||
url: "/*",
|
||||
schema: {
|
||||
hide: true
|
||||
},
|
||||
handler: (req, res) =>
|
||||
nextApp
|
||||
.getRequestHandler()(req.raw, res.raw)
|
||||
|
@ -43,6 +43,7 @@ export const fastifySwagger = fp(async (fastify) => {
|
||||
});
|
||||
|
||||
await fastify.register(swaggerUI, {
|
||||
routePrefix: "/docs"
|
||||
routePrefix: "/api/docs",
|
||||
prefix: "/api/docs"
|
||||
});
|
||||
});
|
||||
|
@ -513,9 +513,9 @@ export const registerRoutes = async (
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: () => {
|
||||
handler: async () => {
|
||||
const cfg = getConfig();
|
||||
const serverCfg = getServerCfg();
|
||||
const serverCfg = await getServerCfg();
|
||||
return {
|
||||
date: new Date(),
|
||||
message: "Ok" as const,
|
||||
|
@ -20,8 +20,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: () => {
|
||||
const config = getServerCfg();
|
||||
handler: async () => {
|
||||
const config = await getServerCfg();
|
||||
return { config };
|
||||
}
|
||||
});
|
||||
@ -78,7 +78,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const appCfg = getConfig();
|
||||
const serverCfg = getServerCfg();
|
||||
const serverCfg = await getServerCfg();
|
||||
if (serverCfg.initialized)
|
||||
throw new UnauthorizedError({ name: "Admin sign up", message: "Admin has been created" });
|
||||
const { user, token } = await server.services.superAdmin.adminSignUp({
|
||||
|
@ -42,7 +42,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
||||
async (req, _accessToken, _refreshToken, profile, cb) => {
|
||||
try {
|
||||
const email = profile?.emails?.[0]?.value;
|
||||
const serverCfg = getServerCfg();
|
||||
const serverCfg = await getServerCfg();
|
||||
if (!email)
|
||||
throw new BadRequestError({
|
||||
message: "Email not found",
|
||||
@ -84,7 +84,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
||||
try {
|
||||
const ghEmails = await fetchGithubEmails(accessToken);
|
||||
const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0];
|
||||
const serverCfg = getServerCfg();
|
||||
const serverCfg = await getServerCfg();
|
||||
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
|
||||
email,
|
||||
firstName: profile.displayName,
|
||||
@ -120,7 +120,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
||||
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {
|
||||
try {
|
||||
const email = profile.emails[0].value;
|
||||
const serverCfg = getServerCfg();
|
||||
const serverCfg = await getServerCfg();
|
||||
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
|
||||
email,
|
||||
firstName: profile.displayName,
|
||||
|
@ -49,6 +49,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
200: z.object({
|
||||
workspaces: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
organization: z.string(),
|
||||
environments: z
|
||||
|
@ -13,6 +13,7 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||
@ -107,6 +108,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath: req.query.secretPath,
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
@ -188,6 +190,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath: req.query.secretPath,
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
@ -255,7 +258,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId: req.body.workspaceId,
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
@ -322,7 +325,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId: req.body.workspaceId,
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
@ -384,7 +387,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId: req.body.workspaceId,
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
@ -467,18 +470,33 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.SecretPulled,
|
||||
distinctId: getDistinctId(req),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
workspaceId: req.query.workspaceId,
|
||||
environment: req.query.environment,
|
||||
secretPath: req.query.secretPath,
|
||||
|
||||
...req.auditLogInfo
|
||||
// TODO: Move to telemetry plugin
|
||||
let shouldRecordK8Event = false;
|
||||
if (req.headers["user-agent"] === "k8-operatoer") {
|
||||
const randomNumber = Math.random();
|
||||
if (randomNumber > 0.95) {
|
||||
shouldRecordK8Event = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const shouldCapture =
|
||||
req.query.workspaceId !== "650e71fbae3e6c8572f436d4" &&
|
||||
(req.headers["user-agent"] !== "k8-operator" || shouldRecordK8Event);
|
||||
const approximateNumberTotalSecrets = secrets.length * 20;
|
||||
if (shouldCapture) {
|
||||
server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.SecretPulled,
|
||||
distinctId: getDistinctId(req),
|
||||
properties: {
|
||||
numberOfSecrets: shouldRecordK8Event ? approximateNumberTotalSecrets : secrets.length,
|
||||
workspaceId: req.query.workspaceId,
|
||||
environment: req.query.environment,
|
||||
secretPath: req.query.secretPath,
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { secrets, imports };
|
||||
}
|
||||
@ -550,7 +568,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId: req.query.workspaceId,
|
||||
environment: req.query.environment,
|
||||
secretPath: req.query.secretPath,
|
||||
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
@ -711,7 +729,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId: req.body.workspaceId,
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
@ -890,7 +908,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId: req.body.workspaceId,
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
@ -1005,7 +1023,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId: req.body.workspaceId,
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
@ -1123,7 +1141,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId: req.body.workspaceId,
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
@ -1240,7 +1258,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId: req.body.workspaceId,
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
@ -1345,7 +1363,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspaceId: req.body.workspaceId,
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
|
@ -108,7 +108,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
await res.setCookie("jid", refreshToken, {
|
||||
void res.setCookie("jid", refreshToken, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
@ -159,7 +159,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
||||
userAgent
|
||||
});
|
||||
|
||||
await res.setCookie("jid", refreshToken, {
|
||||
void res.setCookie("jid", refreshToken, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
|
@ -21,7 +21,11 @@ export const projectDALFactory = (db: TDbClient) => {
|
||||
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||
db.ref("name").withSchema(TableName.Environment).as("envName")
|
||||
)
|
||||
.orderBy("createdAt", "asc", "last");
|
||||
.orderBy([
|
||||
{ column: `${TableName.Project}.name`, order: "asc" },
|
||||
{ column: `${TableName.Environment}.position`, order: "asc" }
|
||||
]);
|
||||
|
||||
const nestedWorkspaces = sqlNestRelationships({
|
||||
data: workspaces,
|
||||
key: "id",
|
||||
@ -41,7 +45,7 @@ export const projectDALFactory = (db: TDbClient) => {
|
||||
|
||||
return nestedWorkspaces.map((workspace) => ({
|
||||
...workspace,
|
||||
organization: workspace.id
|
||||
organization: workspace.orgId
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find all projects" });
|
||||
@ -83,7 +87,7 @@ export const projectDALFactory = (db: TDbClient) => {
|
||||
// We need to add the organization field, as it's required for one of our API endpoint responses.
|
||||
return nestedWorkspaces.map((workspace) => ({
|
||||
...workspace,
|
||||
organization: workspace.id
|
||||
organization: workspace.orgId
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find all projects by identity" });
|
||||
@ -102,7 +106,11 @@ export const projectDALFactory = (db: TDbClient) => {
|
||||
db.ref("id").withSchema(TableName.Environment).as("envId"),
|
||||
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||
db.ref("name").withSchema(TableName.Environment).as("envName")
|
||||
);
|
||||
)
|
||||
.orderBy([
|
||||
{ column: `${TableName.Project}.name`, order: "asc" },
|
||||
{ column: `${TableName.Environment}.position`, order: "asc" }
|
||||
]);
|
||||
return sqlNestRelationships({
|
||||
data: workspaces,
|
||||
key: "id",
|
||||
|
@ -25,10 +25,15 @@ export const fnSecretsFromImports = async ({
|
||||
if (!folderIds.length) {
|
||||
return [];
|
||||
}
|
||||
const importedSecrets = await secretDAL.find({
|
||||
$in: { folderId: folderIds },
|
||||
type: SecretType.Shared
|
||||
});
|
||||
const importedSecrets = await secretDAL.find(
|
||||
{
|
||||
$in: { folderId: folderIds },
|
||||
type: SecretType.Shared
|
||||
},
|
||||
{
|
||||
sort: [["id", "asc"]]
|
||||
}
|
||||
);
|
||||
|
||||
const importedSecsGroupByFolderId = groupBy(importedSecrets, (i) => i.folderId);
|
||||
return allowedImports.map(({ importPath, importEnv }, i) => ({
|
||||
|
@ -47,7 +47,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
||||
|
||||
const tags = await secretTagDAL.find({ projectId });
|
||||
const tags = await secretTagDAL.find({ projectId }, { sort: [["createdAt", "asc"]] });
|
||||
return tags;
|
||||
};
|
||||
|
||||
|
@ -86,7 +86,8 @@ export const secretDALFactory = (db: TDbClient) => {
|
||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
|
||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"))
|
||||
.orderBy("id", "asc");
|
||||
const data = sqlNestRelationships({
|
||||
data: secs,
|
||||
key: "id",
|
||||
|
@ -117,7 +117,7 @@ export const serviceTokenServiceFactory = ({
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
||||
|
||||
const tokens = await serviceTokenDAL.find({ projectId });
|
||||
const tokens = await serviceTokenDAL.find({ projectId }, { sort: [["createdAt", "desc"]] });
|
||||
return tokens;
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
|
||||
import { TAuthLoginFactory } from "../auth/auth-login-service";
|
||||
@ -17,11 +18,8 @@ type TSuperAdminServiceFactoryDep = {
|
||||
|
||||
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
|
||||
|
||||
let serverCfg: Readonly<TSuperAdmin>;
|
||||
export const getServerCfg = () => {
|
||||
if (!serverCfg) throw new BadRequestError({ name: "Get server cfg", message: "Server cfg not initialized" });
|
||||
return serverCfg;
|
||||
};
|
||||
// eslint-disable-next-line
|
||||
export let getServerCfg: () => Promise<TSuperAdmin>;
|
||||
|
||||
export const superAdminServiceFactory = ({
|
||||
serverCfgDAL,
|
||||
@ -30,19 +28,18 @@ export const superAdminServiceFactory = ({
|
||||
orgService
|
||||
}: TSuperAdminServiceFactoryDep) => {
|
||||
const initServerCfg = async () => {
|
||||
serverCfg = await serverCfgDAL.findOne({});
|
||||
if (!serverCfg) {
|
||||
const newCfg = await serverCfgDAL.create({ initialized: false, allowSignUp: true });
|
||||
serverCfg = newCfg;
|
||||
return newCfg;
|
||||
}
|
||||
return serverCfg;
|
||||
// TODO(akhilmhdh): bad pattern time less change this later to me itself
|
||||
getServerCfg = () => serverCfgDAL.findOne({});
|
||||
|
||||
const serverCfg = await serverCfgDAL.findOne({});
|
||||
if (serverCfg) return;
|
||||
const newCfg = await serverCfgDAL.create({ initialized: false, allowSignUp: true });
|
||||
return newCfg;
|
||||
};
|
||||
|
||||
const updateServerCfg = async (data: TSuperAdminUpdate) => {
|
||||
const serverCfg = await getServerCfg();
|
||||
const cfg = await serverCfgDAL.updateById(serverCfg.id, data);
|
||||
serverCfg = cfg;
|
||||
Object.freeze(serverCfg);
|
||||
return cfg;
|
||||
};
|
||||
|
||||
@ -62,6 +59,7 @@ export const superAdminServiceFactory = ({
|
||||
ip,
|
||||
userAgent
|
||||
}: TAdminSignUpDTO) => {
|
||||
const appCfg = getConfig();
|
||||
const existingUser = await userDAL.findOne({ email });
|
||||
if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exist" });
|
||||
|
||||
@ -95,7 +93,10 @@ export const superAdminServiceFactory = ({
|
||||
);
|
||||
return { user: newUser, enc: userEnc };
|
||||
});
|
||||
await orgService.createOrganization(userInfo.user.id, userInfo.user.email, "Admin Org");
|
||||
|
||||
const initialOrganizationName = appCfg.INITIAL_ORGANIZATION_NAME ?? "Admin Org";
|
||||
|
||||
await orgService.createOrganization(userInfo.user.id, userInfo.user.email, initialOrganizationName);
|
||||
|
||||
await updateServerCfg({ initialized: true });
|
||||
const token = await authService.generateUserTokens(userInfo.user, ip, userAgent);
|
||||
|
@ -80,7 +80,8 @@ export const webhookDALFactory = (db: TDbClient) => {
|
||||
.select(db.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
||||
.select(db.ref("id").withSchema(TableName.Environment).as("envId"))
|
||||
.select(db.ref("projectId").withSchema(TableName.Environment))
|
||||
.select(selectAllTableCols(TableName.Webhook));
|
||||
.select(selectAllTableCols(TableName.Webhook))
|
||||
.orderBy(`${TableName.Webhook}.createdAt`, "asc");
|
||||
|
||||
return webhooks.map(({ envId, envSlug, envName, ...el }) => ({
|
||||
...el,
|
||||
|
@ -22,11 +22,9 @@
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@app/*": ["./src/*"],
|
||||
"@lib/*": ["./src/lib/*"],
|
||||
"@server/*": ["./src/server/*"]
|
||||
"@app/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*", "scripts/**/*", "e2e-test/**/*","./.eslintrc.js"],
|
||||
"include": ["src/**/*", "scripts/**/*", "e2e-test/**/*", "./.eslintrc.js", "./tsup.config.js"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
@ -1,14 +1,73 @@
|
||||
/* eslint-disable */
|
||||
import path from "node:path";
|
||||
|
||||
import fs from "fs/promises";
|
||||
import { replaceTscAliasPaths } from "tsc-alias";
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
// Instead of using tsx or tsc for building, consider using tsup.
|
||||
// TSX serves as an alternative to Node.js, allowing you to build directly on the Node.js runtime.
|
||||
// Its functionality mirrors Node.js, with the only difference being the absence of a final build step. Production should ideally be launched with TSX.
|
||||
// TSC is effective for creating a final build, but it requires manual copying of all static files such as handlebars, emails, etc.
|
||||
// A significant challenge is the shift towards ESM, as more packages are adopting ESM. If the output is in CommonJS, it may lead to errors.
|
||||
// The suggested configuration offers a balance, accommodating both ESM and CommonJS requirements.
|
||||
|
||||
export default defineConfig({
|
||||
shims: true,
|
||||
clean: true,
|
||||
minify: false,
|
||||
keepNames: true,
|
||||
splitting: false,
|
||||
format: "esm",
|
||||
// copy the files to output
|
||||
loader: {
|
||||
".handlebars": "copy",
|
||||
".md": "copy"
|
||||
".md": "copy",
|
||||
".txt": "copy"
|
||||
},
|
||||
external: ["../../../frontend/node_modules/next/dist/server/next-server.js"],
|
||||
outDir: "dist",
|
||||
tsconfig: "./tsconfig.json",
|
||||
entry: ["./src"],
|
||||
sourceMap: "inline"
|
||||
sourceMap: true,
|
||||
skipNodeModulesBundle: true,
|
||||
esbuildPlugins: [
|
||||
{
|
||||
// esm directory import are not allowed
|
||||
// /folder1 should be explicitly imported as /folder1/index.ts
|
||||
// this plugin will append it automatically on build time to all imports
|
||||
name: "commonjs-esm-directory-import",
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /.*/ }, async (args) => {
|
||||
if (args.importer) {
|
||||
if (args.kind === "import-statement") {
|
||||
const isRelativePath = args.path.startsWith(".");
|
||||
const absPath = isRelativePath
|
||||
? path.join(args.resolveDir, args.path)
|
||||
: path.join(args.path.replace("@app", "./src"));
|
||||
|
||||
const isFile = await fs
|
||||
.stat(`${absPath}.ts`)
|
||||
.then((el) => el.isFile)
|
||||
.catch((err) => err.code === "ENOTDIR");
|
||||
|
||||
return {
|
||||
path: isFile ? `${args.path}.mjs` : `${args.path}/index.mjs`,
|
||||
external: true
|
||||
};
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
async onSuccess() {
|
||||
// this will replace all tsconfig paths
|
||||
await replaceTscAliasPaths({
|
||||
configFile: "tsconfig.json",
|
||||
watch: false,
|
||||
outDir: "dist"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -34,6 +34,17 @@ services:
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
redis-commander:
|
||||
container_name: infisical-dev-redis-commander
|
||||
image: rediscommander/redis-commander
|
||||
restart: always
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- REDIS_HOSTS=local:redis:6379
|
||||
ports:
|
||||
- "8085:8081"
|
||||
|
||||
db-test:
|
||||
profiles: ["test"]
|
||||
image: postgres:14-alpine
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Role-based Access Controls"
|
||||
description: "Infisical's Role-based Acccess Controls enable creating permissions for user and machine identities to restrict access to resources and the range of actions that can performed."
|
||||
description: "Infisical's Role-based Access Controls enable creating permissions for user and machine identities to restrict access to resources and the range of actions that can be performed."
|
||||
---
|
||||
|
||||
### General access controls
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 134 KiB |
Binary file not shown.
Before Width: | Height: | Size: 191 KiB |
Binary file not shown.
Before Width: | Height: | Size: 117 KiB |
Binary file not shown.
Before Width: | Height: | Size: 135 KiB |
Binary file not shown.
Before Width: | Height: | Size: 107 KiB |
@ -1,287 +0,0 @@
|
||||
---
|
||||
title: 'Amazon ECS'
|
||||
description: "How to deliver secrets to Amazon Elastic Container Service"
|
||||
---
|
||||
|
||||

|
||||
|
||||
This guide will go over the steps needed to access secrets stored in Infisical from Amazon Elastic Container Service (ECS).
|
||||
|
||||
At a high level, the steps involve setting up an ECS task with a [Infisical Agent](/infisical-agent/overview) as a sidecar container. This sidecar container uses [Universal Auth](/documentation/platform/identities/universal-auth) to authenticate with Infisical to fetch secrets/access tokens.
|
||||
Once the secrets/access tokens are retrieved, they are then stored in a shared [Amazon Elastic File System](https://aws.amazon.com/efs/) (EFS) volume. This volume is then made accessible to your application and all of its replicas.
|
||||
|
||||
This guide primarily focuses on integrating Infisical Cloud with Amazon ECS on AWS Fargate and Amazon EFS.
|
||||
However, the principles and steps can be adapted for use with any instance of Infisical (on premise or cloud) and different ECS launch configurations.
|
||||
|
||||
## Prerequisites
|
||||
This guide requires the following prerequisites:
|
||||
- Infisical account
|
||||
- Git installed
|
||||
- Terraform v1.0 or later installed
|
||||
- Access to AWS credentials
|
||||
- Understanding of [Infisical Agent](/infisical-agent/overview)
|
||||
|
||||
## What we will deploy
|
||||
For this demonstration, we'll deploy the [File Browser](https://github.com/filebrowser/filebrowser) application on our ECS cluster.
|
||||
Although this guide focuses on File Browser, the principles outlined here can be applied to any application of your choice.
|
||||
|
||||
File Browser plays a key role in this context because it enables us to view all files attached to a specific volume.
|
||||
This feature is important for our demonstration, as it allows us to verify whether the Infisical agent is depositing the expected files into the designated file volume and if those files are accessible to the application.
|
||||
|
||||
<Warning>
|
||||
Volumes that contain sensitive secrets should not be publicly accessible. The use of File Browser here is solely for demonstration and verification purposes.
|
||||
</Warning>
|
||||
|
||||
|
||||
## Configure Authentication with Infisical
|
||||
In order for the Infisical agent to fetch credentials from Infisical, we'll first need to authenticate with Infisical.
|
||||
While Infisical supports various authentication methods, this guide focuses on using Universal Auth to authenticate the agent with Infisical.
|
||||
|
||||
Follow the documentation to configure and generate a client id and client secret with Universal auth [here](/documentation/platform/identities/universal-auth).
|
||||
Make sure to save these credentials somewhere handy because you'll need them soon.
|
||||
|
||||
## Clone guide assets repository
|
||||
To help you quickly deploy the example application, please clone the guide assets from this [Github repository](https://github.com/Infisical/infisical-guides.git).
|
||||
This repository contains assets for all Infisical guides. The content for this guide can be found within a sub directory called `aws-ecs-with-agent`.
|
||||
The guide will assume that `aws-ecs-with-agent` is your working directory going forward.
|
||||
|
||||
## Deploy example application
|
||||
|
||||
Before we can deploy our full application and its related infrastructure with Terraform, we'll need to first configure our Infisical agent.
|
||||
|
||||
### Agent configuration overview
|
||||
The agent config file defines what authentication method will be used when connecting with Infisical along with where the fetched secrets/access tokens should be saved to.
|
||||
|
||||
Since the Infisical agent will be deployed as a sidecar, the agent configuration file and any secret template files will need to be encoded in base64.
|
||||
This encoding step is necessary as it allows these files to be added into our Terraform configuration file without needing to upload them first.
|
||||
|
||||
#### Secret template file
|
||||
The Infisical agent accepts one or more optional template files. If provided, the agent will fetch secrets using the set authentication method and format the fetched secrets according to the given template file.
|
||||
|
||||
For demonstration purposes, we will create the following secret template file.
|
||||
This template will transform our secrets from Infisical project with the ID `62fd92aa8b63973fee23dec7`, in the `dev` environment, and secrets located in the path `/`, into a `KEY=VALUE` format.
|
||||
|
||||
<Tip>
|
||||
Remember to update the project id, environment slug and secret path to one that exists within your Infisical project
|
||||
</Tip>
|
||||
|
||||
```secrets.template secrets.template
|
||||
{{- with secret "62fd92aa8b63973fee23dec7" "dev" "/" }}
|
||||
{{- range . }}
|
||||
{{ .Key }}={{ .Value }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
Next, we need encode this template file in `base64` so it can be set in the agent configuration file.
|
||||
|
||||
```bash
|
||||
cat secrets.template | base64
|
||||
Cnt7LSB3aXRoIHNlY3JldCAiMWVkMjk2MWQtNDM5NS00MmNlLTlkNzQtYjk2ZGQwYmYzMDg0IiAiZGV2IiAiLyIgfX0Ke3stIHJhbmdlIC4gfX0Ke3sgLktleSB9fT17eyAuVmFsdWUgfX0Ke3stIGVuZCB9fQp7ey0gZW5kIH19
|
||||
```
|
||||
|
||||
#### Full agent configuration file
|
||||
This agent config file will connect with Infisical Cloud using Universal Auth and deposit access tokens at path `/infisical-agent/access-token` and render secrets to file `/infisical-agent/secrets`.
|
||||
|
||||
You'll notice that instead of passing the path to the secret template file as we normally would, we set the base64 encoded template from the previous step under `base64-template-content` property.
|
||||
|
||||
```yaml agent-config.yaml
|
||||
infisical:
|
||||
address: https://app.infisical.com
|
||||
exit-after-auth: true
|
||||
auth:
|
||||
type: universal-auth
|
||||
config:
|
||||
remove_client_secret_on_read: false
|
||||
sinks:
|
||||
- type: file
|
||||
config:
|
||||
path: /infisical-agent/access-token
|
||||
templates:
|
||||
- base64-template-content: Cnt7LSB3aXRoIHNlY3JldCAiMWVkMjk2MWQtNDM5NS00MmNlLTlkNzQtYjk2ZGQwYmYzMDg0IiAiZGV2IiAiLyIgfX0Ke3stIHJhbmdlIC4gfX0Ke3sgLktleSB9fT17eyAuVmFsdWUgfX0Ke3stIGVuZCB9fQp7ey0gZW5kIH19
|
||||
destination-path: /infisical-agent/secrets
|
||||
```
|
||||
|
||||
Again, we'll need to encode the full configuration file in `base64` so it can be easily delivered via Terraform.
|
||||
|
||||
```bash
|
||||
cat agent-config.yaml | base64
|
||||
aW5maXNpY2FsOgogIGFkZHJlc3M6IGh0dHBzOi8vYXBwLmluZmlzaWNhbC5jb20KICBleGl0LWFmdGVyLWF1dGg6IHRydWUKYXV0aDoKICB0eXBlOiB1bml2ZXJzYWwtYXV0aAogIGNvbmZpZzoKICAgIHJlbW92ZV9jbGllbnRfc2VjcmV0X29uX3JlYWQ6IGZhbHNlCnNpbmtzOgogIC0gdHlwZTogZmlsZQogICAgY29uZmlnOgogICAgICBwYXRoOiAvaW5maXNpY2FsLWFnZW50L2FjY2Vzcy10b2tlbgp0ZW1wbGF0ZXM6CiAgLSBiYXNlNjQtdGVtcGxhdGUtY29udGVudDogQ250N0xTQjNhWFJvSUhObFkzSmxkQ0FpTVdWa01qazJNV1F0TkRNNU5TMDBNbU5sTFRsa056UXRZamsyWkdRd1ltWXpNRGcwSWlBaVpHVjJJaUFpTHlJZ2ZYMEtlM3N0SUhKaGJtZGxJQzRnZlgwS2Uzc2dMa3RsZVNCOWZUMTdleUF1Vm1Gc2RXVWdmWDBLZTNzdElHVnVaQ0I5ZlFwN2V5MGdaVzVrSUgxOQogICAgZGVzdGluYXRpb24tcGF0aDogL2luZmlzaWNhbC1hZ2VudC9zZWNyZXRzCg==
|
||||
```
|
||||
|
||||
## Add auth credentials & agent config
|
||||
With the base64 encoded agent config file and Universal Auth credentials in hand, it's time to assign them as values in our Terraform config file.
|
||||
|
||||
To configure these values, navigate to the `ecs.tf` file in your preferred code editor and assign values to `auth_client_id`, `auth_client_secret`, and `agent_config`.
|
||||
|
||||
```hcl ecs.tf
|
||||
...snip...
|
||||
data "template_file" "cb_app" {
|
||||
template = file("./templates/ecs/cb_app.json.tpl")
|
||||
|
||||
vars = {
|
||||
app_image = var.app_image
|
||||
sidecar_image = var.sidecar_image
|
||||
app_port = var.app_port
|
||||
fargate_cpu = var.fargate_cpu
|
||||
fargate_memory = var.fargate_memory
|
||||
aws_region = var.aws_region
|
||||
auth_client_id = "<paste-client-id-string>"
|
||||
auth_client_secret = "<paset-client-secret-string>"
|
||||
agent_config = "<paste-base64-encoded-agent-config-string>"
|
||||
}
|
||||
}
|
||||
...snip...
|
||||
```
|
||||
|
||||
<Warning>
|
||||
To keep this guide simple, `auth_client_id`, `auth_client_secret` have been added directly into the ECS container definition.
|
||||
However, in production, you should securely fetch these values from AWS Secrets Manager or AWS Parameter store and feed them directly to agent sidecar.
|
||||
</Warning>
|
||||
|
||||
After these values have been set, they will be passed to the Infisical agent during startup through environment variables, as configured in the `infisical-sidecar` container below.
|
||||
|
||||
```terraform templates/ecs/cb_app.json.tpl
|
||||
[
|
||||
...snip...
|
||||
{
|
||||
"name": "infisical-sidecar",
|
||||
"image": "${sidecar_image}",
|
||||
"cpu": 1024,
|
||||
"memory": 1024,
|
||||
"networkMode": "bridge",
|
||||
"command": ["agent"],
|
||||
"essential": false,
|
||||
"logConfiguration": {
|
||||
"logDriver": "awslogs",
|
||||
"options": {
|
||||
"awslogs-group": "/ecs/agent",
|
||||
"awslogs-region": "${aws_region}",
|
||||
"awslogs-stream-prefix": "ecs"
|
||||
}
|
||||
},
|
||||
"healthCheck": {
|
||||
"command": ["CMD-SHELL", "agent", "--help"],
|
||||
"interval": 30,
|
||||
"timeout": 5,
|
||||
"retries": 3,
|
||||
"startPeriod": 0
|
||||
},
|
||||
"environment": [
|
||||
{
|
||||
"name": "INFISICAL_UNIVERSAL_AUTH_CLIENT_ID",
|
||||
"value": "${auth_client_id}"
|
||||
},
|
||||
{
|
||||
"name": "INFISICAL_UNIVERSAL_CLIENT_SECRET",
|
||||
"value": "${auth_client_secret}"
|
||||
},
|
||||
{
|
||||
"name": "INFISICAL_AGENT_CONFIG_BASE64",
|
||||
"value": "${agent_config}"
|
||||
}
|
||||
],
|
||||
"mountPoints": [
|
||||
{
|
||||
"containerPath": "/infisical-agent",
|
||||
"sourceVolume": "infisical-efs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
In the above container definition, you'll notice that that the Infisical agent has a `mountPoints` defined.
|
||||
This mount point is referencing to the already configured EFS volume as shown below.
|
||||
`containerPath` is set to `/infisical-agent` because that is that the folder we have instructed the agent to deposit the credentials to.
|
||||
|
||||
```hcl terraform/efs.tf
|
||||
resource "aws_efs_file_system" "infisical_efs" {
|
||||
tags = {
|
||||
Name = "INFISICAL-ECS-EFS"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_efs_mount_target" "mount" {
|
||||
count = length(aws_subnet.private.*.id)
|
||||
file_system_id = aws_efs_file_system.infisical_efs.id
|
||||
subnet_id = aws_subnet.private[count.index].id
|
||||
security_groups = [aws_security_group.efs_sg.id]
|
||||
}
|
||||
```
|
||||
|
||||
## Configure AWS credentials
|
||||
Because we'll be deploying the example file browser application to AWS via Terraform, you will need to obtain a set of `AWS Access Key` and `Secret Key`.
|
||||
Once you have generated these credentials, export them to your terminal.
|
||||
|
||||
1. Export the AWS Access Key ID:
|
||||
|
||||
```bash
|
||||
export AWS_ACCESS_KEY_ID=<your AWS access key ID>
|
||||
```
|
||||
|
||||
2. Export the AWS Secret Access Key:
|
||||
|
||||
```bash
|
||||
export AWS_SECRET_ACCESS_KEY=<your AWS secret access key>
|
||||
```
|
||||
|
||||
## Deploy terraform configuration
|
||||
With the agent's sidecar configuration complete, we can now deploy our changes to AWS via Terraform.
|
||||
|
||||
1. Change your directory to `terraform`
|
||||
```sh
|
||||
cd terraform
|
||||
```
|
||||
|
||||
2. Initialize Terraform
|
||||
```
|
||||
$ terraform init
|
||||
```
|
||||
|
||||
3. Preview resources that will be created
|
||||
```
|
||||
$ terraform plan
|
||||
```
|
||||
|
||||
4. Trigger resource creation
|
||||
```bash
|
||||
$ terraform apply
|
||||
|
||||
Do you want to perform these actions?
|
||||
Terraform will perform the actions described above.
|
||||
Only 'yes' will be accepted to approve.
|
||||
|
||||
Enter a value: yes
|
||||
```
|
||||
|
||||
```bash
|
||||
|
||||
Apply complete! Resources: 1 added, 1 changed, 1 destroyed.
|
||||
|
||||
Outputs:
|
||||
|
||||
alb_hostname = "cb-load-balancer-1675475779.us-east-1.elb.amazonaws.com:8080"
|
||||
```
|
||||
|
||||
Once the resources have been successfully deployed, Terrafrom will output the host address where the file browser application will be accessible.
|
||||
It may take a few minutes for the application to become fully ready.
|
||||
|
||||
|
||||
## Verify secrets/tokens in EFS volume
|
||||
To verify that the agent is depositing access tokens and rendering secrets to the paths specified in the agent config, navigate to the web address from the previous step.
|
||||
Once you visit the address, you'll be prompted to login. Enter the credentials shown below.
|
||||
|
||||

|
||||
|
||||
Since our EFS volume is mounted to the path of the file browser application, we should see the access token and rendered secret file we defined via the agent config file.
|
||||
|
||||

|
||||
|
||||
As expected, two files are present: `access-token` and `secrets`.
|
||||
The `access-token` file should hold a valid `Bearer` token, which can be used to make HTTP requests to Infisical.
|
||||
The `secrets` file should contain secrets, formatted according to the specifications in our secret template file (presented in key=value format).
|
||||
|
||||

|
||||
|
||||

|
@ -1,9 +1,8 @@
|
||||
{
|
||||
"name": "Infisical",
|
||||
"basePath": "/docs",
|
||||
"logo": {
|
||||
"dark": "/docs/logo/dark.svg",
|
||||
"light": "/docs/logo/light.svg",
|
||||
"dark": "/logo/dark.svg",
|
||||
"light": "/logo/light.svg",
|
||||
"href": "https://infisical.com"
|
||||
},
|
||||
"favicon": "/favicon.png",
|
||||
@ -234,8 +233,7 @@
|
||||
},
|
||||
"integrations/platforms/kubernetes",
|
||||
"integrations/frameworks/terraform",
|
||||
"integrations/platforms/ansible",
|
||||
"integrations/platforms/ecs-with-agent"
|
||||
"integrations/platforms/ansible"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -30,35 +30,35 @@ export const AdminDashboardPage = () => {
|
||||
|
||||
return (
|
||||
<div className="container mx-auto max-w-7xl pb-12 text-white dark:[color-scheme:dark]">
|
||||
<div className="mb-8">
|
||||
<div className="mb-4 mt-6 flex flex-col items-start justify-between text-xl">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl py-6 px-6">
|
||||
<div className="mb-8 flex flex-col items-start justify-between text-xl">
|
||||
<h1 className="text-3xl font-semibold">Admin Dashboard</h1>
|
||||
<p className="text-base text-bunker-300">Manage your Infisical</p>
|
||||
<p className="text-base text-bunker-300">Manage your Infisical instance.</p>
|
||||
</div>
|
||||
{isUserLoading || isNotAllowed ? (
|
||||
<ContentLoader text={isNotAllowed ? "Redirecting to org page..." : undefined} />
|
||||
) : (
|
||||
<div>
|
||||
<Tabs defaultValue={TabSections.Settings}>
|
||||
<TabList>
|
||||
<div className="flex w-full flex-row border-b border-mineshaft-600">
|
||||
<Tab value={TabSections.Settings}>General</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Settings}>
|
||||
<div className="flex items-center space-x-4">
|
||||
<Switch
|
||||
id="disable-invite"
|
||||
isChecked={Boolean(config?.allowSignUp)}
|
||||
onCheckedChange={(isChecked) => updateServerConfig({ allowSignUp: isChecked })}
|
||||
/>
|
||||
<div className="flex-grow">Enable signup or invite</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isUserLoading || isNotAllowed ? (
|
||||
<ContentLoader text={isNotAllowed ? "Redirecting to org page..." : undefined} />
|
||||
) : (
|
||||
<div>
|
||||
<Tabs defaultValue={TabSections.Settings}>
|
||||
<TabList>
|
||||
<div className="flex w-full flex-row border-b border-mineshaft-600">
|
||||
<Tab value={TabSections.Settings}>General</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Settings}>
|
||||
<div className="flex items-center space-x-4">
|
||||
<Switch
|
||||
id="disable-invite"
|
||||
isChecked={Boolean(config?.allowSignUp)}
|
||||
onCheckedChange={(isChecked) => updateServerConfig({ allowSignUp: isChecked })}
|
||||
/>
|
||||
<div className="flex-grow">Enable signup or invite</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -61,7 +61,7 @@ export const SignUpPage = () => {
|
||||
router.push("/login");
|
||||
}
|
||||
}
|
||||
}, [config?.initialized]);
|
||||
}, []);
|
||||
|
||||
const { mutateAsync: createAdminUser } = useCreateAdminUser();
|
||||
|
||||
|
Reference in New Issue
Block a user