mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-28 02:53:22 +00:00
Compare commits
43 Commits
infisical/
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
|
d11fda3be5 | ||
|
0df5f845fb | ||
|
ca59488b62 | ||
|
3a05ae4b27 | ||
|
dd009182e8 | ||
|
8a17cd3f5d | ||
|
99fe43f459 | ||
|
79196b0081 | ||
|
b76ff28414 | ||
|
2894cf791a | ||
|
c040b0ca9a | ||
|
15f60aa7dd | ||
|
6f68d304ea | ||
|
309a106f13 | ||
|
74d73590a1 | ||
|
b42b5614c9 | ||
|
72b89cb989 | ||
|
36d8b22598 | ||
|
201dcd971c | ||
|
ab90745312 | ||
|
622106045e | ||
|
e64302b789 | ||
|
901a7fc294 | ||
|
359694dd47 | ||
|
57489a7578 | ||
|
a4205a8662 | ||
|
dbf177d667 | ||
|
f078aec54c | ||
|
5dfe62e306 | ||
|
b89925c61c | ||
|
6d0bea6d5f | ||
|
10a40c8ab2 | ||
|
b910ceacfc | ||
|
cb66386e13 | ||
|
889df3dcb1 | ||
|
ae53f03f71 | ||
|
7ae024724d | ||
|
0b2bc1d345 | ||
|
da5eca3e68 | ||
|
3375d3ff85 | ||
|
d140e4f3c9 | ||
|
8e25631fb0 | ||
|
0912903e0d |
@@ -23,6 +23,8 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: 🔧 Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- run: git fetch --force --tags
|
||||
- run: echo "Ref name ${{github.ref_name}}"
|
||||
- uses: actions/setup-go@v3
|
||||
|
@@ -190,10 +190,34 @@ dockers:
|
||||
- dockerfile: docker/alpine
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
use: buildx
|
||||
ids:
|
||||
- all-other-builds
|
||||
image_templates:
|
||||
- "infisical/cli:{{ .Version }}"
|
||||
- "infisical/cli:{{ .Major }}.{{ .Minor }}"
|
||||
- "infisical/cli:{{ .Major }}"
|
||||
- "infisical/cli:latest"
|
||||
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-amd64"
|
||||
- "infisical/cli:latest-amd64"
|
||||
build_flag_templates:
|
||||
- "--pull"
|
||||
- "--platform=linux/amd64"
|
||||
- dockerfile: docker/alpine
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
use: buildx
|
||||
ids:
|
||||
- all-other-builds
|
||||
image_templates:
|
||||
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-arm64"
|
||||
- "infisical/cli:latest-arm64"
|
||||
build_flag_templates:
|
||||
- "--pull"
|
||||
- "--platform=linux/arm64"
|
||||
|
||||
docker_manifests:
|
||||
- name_template: "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}"
|
||||
image_templates:
|
||||
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-amd64"
|
||||
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-arm64"
|
||||
- name_template: "infisical/cli:latest"
|
||||
image_templates:
|
||||
- "infisical/cli:latest-amd64"
|
||||
- "infisical/cli:latest-arm64"
|
||||
|
@@ -9,7 +9,12 @@ export async function seed(knex: Knex): Promise<void> {
|
||||
await knex(TableName.Users).del();
|
||||
await knex(TableName.UserEncryptionKey).del();
|
||||
await knex(TableName.SuperAdmin).del();
|
||||
await knex(TableName.SuperAdmin).insert([{ initialized: true, allowSignUp: true }]);
|
||||
|
||||
await knex(TableName.SuperAdmin).insert([
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
{ id: "00000000-0000-0000-0000-000000000000", initialized: true, allowSignUp: true }
|
||||
]);
|
||||
// Inserts seed entries
|
||||
const [user] = await knex(TableName.Users)
|
||||
.insert([
|
||||
|
@@ -263,6 +263,8 @@ export const registerRoutes = async (
|
||||
incidentContactDAL,
|
||||
tokenService,
|
||||
projectDAL,
|
||||
projectMembershipDAL,
|
||||
projectKeyDAL,
|
||||
smtpService,
|
||||
userDAL,
|
||||
orgBotDAL
|
||||
|
@@ -649,33 +649,21 @@ export const integrationAuthServiceFactory = ({
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
|
||||
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
|
||||
if (appId) {
|
||||
|
||||
if (appId && appId !== "") {
|
||||
const query = `
|
||||
query project($id: String!) {
|
||||
project(id: $id) {
|
||||
createdAt
|
||||
deletedAt
|
||||
id
|
||||
description
|
||||
expiredAt
|
||||
isPublic
|
||||
isTempProject
|
||||
isUpdatable
|
||||
name
|
||||
prDeploys
|
||||
teamId
|
||||
updatedAt
|
||||
upstreamUrl
|
||||
services {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
query project($id: String!) {
|
||||
project(id: $id) {
|
||||
services {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables = {
|
||||
@@ -711,6 +699,7 @@ export const integrationAuthServiceFactory = ({
|
||||
);
|
||||
return edges.map(({ node: { name, id: serviceId } }) => ({ name, serviceId }));
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
|
@@ -1204,21 +1204,21 @@ const syncSecretsRailway = async ({
|
||||
}
|
||||
`;
|
||||
|
||||
const input = {
|
||||
projectId: integration.appId,
|
||||
environmentId: integration.targetEnvironmentId,
|
||||
...(integration.targetServiceId ? { serviceId: integration.targetServiceId } : {}),
|
||||
replace: true,
|
||||
variables: getSecretKeyValuePair(secrets)
|
||||
const variables = {
|
||||
input: {
|
||||
projectId: integration.appId,
|
||||
environmentId: integration.targetEnvironmentId,
|
||||
...(integration.targetServiceId ? { serviceId: integration.targetServiceId } : {}),
|
||||
replace: true,
|
||||
variables: getSecretKeyValuePair(secrets)
|
||||
}
|
||||
};
|
||||
|
||||
await request.post(
|
||||
IntegrationUrls.RAILWAY_API_URL,
|
||||
{
|
||||
query,
|
||||
variables: {
|
||||
input
|
||||
}
|
||||
variables
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
|
@@ -22,6 +22,8 @@ import { ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type";
|
||||
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
||||
import { TokenType } from "../auth-token/auth-token-types";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||
import { TUserDALFactory } from "../user/user-dal";
|
||||
import { TIncidentContactsDALFactory } from "./incident-contacts-dal";
|
||||
@@ -44,6 +46,8 @@ type TOrgServiceFactoryDep = {
|
||||
orgRoleDAL: TOrgRoleDALFactory;
|
||||
userDAL: TUserDALFactory;
|
||||
projectDAL: TProjectDALFactory;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserId" | "delete">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
|
||||
incidentContactDAL: TIncidentContactsDALFactory;
|
||||
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne" | "findEnforceableSamlCfg">;
|
||||
smtpService: TSmtpService;
|
||||
@@ -65,6 +69,8 @@ export const orgServiceFactory = ({
|
||||
permissionService,
|
||||
smtpService,
|
||||
projectDAL,
|
||||
projectMembershipDAL,
|
||||
projectKeyDAL,
|
||||
tokenService,
|
||||
orgBotDAL,
|
||||
licenseService,
|
||||
@@ -503,10 +509,50 @@ export const orgServiceFactory = ({
|
||||
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
|
||||
|
||||
const membership = await orgDAL.deleteMembershipById(membershipId, orgId);
|
||||
const deletedMembership = await orgDAL.transaction(async (tx) => {
|
||||
const orgMembership = await orgDAL.deleteMembershipById(membershipId, orgId, tx);
|
||||
|
||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||
return membership;
|
||||
if (!orgMembership.userId) {
|
||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||
return orgMembership;
|
||||
}
|
||||
|
||||
// Get all the project memberships of the user in the organization
|
||||
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, orgMembership.userId);
|
||||
|
||||
// Delete all the project memberships of the user in the organization
|
||||
await projectMembershipDAL.delete(
|
||||
{
|
||||
$in: {
|
||||
id: projectMemberships.map((membership) => membership.id)
|
||||
}
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
// Get all the project keys of the user in the organization
|
||||
const projectKeys = await projectKeyDAL.find({
|
||||
$in: {
|
||||
projectId: projectMemberships.map((membership) => membership.projectId)
|
||||
},
|
||||
receiverId: orgMembership.userId
|
||||
});
|
||||
|
||||
// Delete all the project keys of the user in the organization
|
||||
await projectKeyDAL.delete(
|
||||
{
|
||||
$in: {
|
||||
id: projectKeys.map((key) => key.id)
|
||||
}
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||
return orgMembership;
|
||||
});
|
||||
|
||||
return deletedMembership;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@@ -82,5 +82,25 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
return { ...projectMemberOrm, findAllProjectMembers, findProjectGhostUser, findMembershipsByEmail };
|
||||
const findProjectMembershipsByUserId = async (orgId: string, userId: string) => {
|
||||
try {
|
||||
const memberships = await db(TableName.ProjectMembership)
|
||||
.where({ userId })
|
||||
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||
.where({ [`${TableName.Project}.orgId` as "orgId"]: orgId })
|
||||
.select(selectAllTableCols(TableName.ProjectMembership));
|
||||
|
||||
return memberships;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find project memberships by user id" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...projectMemberOrm,
|
||||
findAllProjectMembers,
|
||||
findProjectGhostUser,
|
||||
findMembershipsByEmail,
|
||||
findProjectMembershipsByUserId
|
||||
};
|
||||
};
|
||||
|
@@ -102,8 +102,11 @@ export const projectQueueFactory = ({
|
||||
|
||||
const oldProjectKey = await projectKeyDAL.findLatestProjectKey(data.startedByUserId, data.projectId);
|
||||
|
||||
if (!project || !oldProjectKey) {
|
||||
throw new Error("Project or project key not found");
|
||||
if (!project) {
|
||||
throw new Error("Project not found");
|
||||
}
|
||||
if (!oldProjectKey) {
|
||||
throw new Error("Old project key not found");
|
||||
}
|
||||
|
||||
if (project.upgradeStatus !== ProjectUpgradeStatus.Failed && project.upgradeStatus !== null) {
|
||||
@@ -267,8 +270,19 @@ export const projectQueueFactory = ({
|
||||
const user = await userDAL.findUserEncKeyByUserId(key.receiverId);
|
||||
const [orgMembership] = await orgDAL.findMembership({ userId: key.receiverId, orgId: project.orgId });
|
||||
|
||||
if (!user || !orgMembership) {
|
||||
throw new Error(`User with ID ${key.receiverId} was not found during upgrade, or user is not in org.`);
|
||||
if (!user) {
|
||||
throw new Error(`User with ID ${key.receiverId} was not found during upgrade.`);
|
||||
}
|
||||
|
||||
if (!orgMembership) {
|
||||
// This can happen. Since we don't remove project memberships and project keys when a user is removed from an org, this is a valid case.
|
||||
logger.info("User is not in organization", {
|
||||
userId: key.receiverId,
|
||||
orgId: project.orgId,
|
||||
projectId: project.id
|
||||
});
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
const [newMember] = assignWorkspaceKeysToMembers({
|
||||
@@ -532,7 +546,12 @@ export const projectQueueFactory = ({
|
||||
logger.error("Failed to upgrade project, because no project was found", data);
|
||||
} else {
|
||||
await projectDAL.setProjectUpgradeStatus(data.projectId, ProjectUpgradeStatus.Failed);
|
||||
logger.error(err, "Failed to upgrade project");
|
||||
logger.error("Failed to upgrade project", err, {
|
||||
extra: {
|
||||
project,
|
||||
jobData: data
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
throw err;
|
||||
|
@@ -299,10 +299,10 @@ type GetFoldersV1Response struct {
|
||||
}
|
||||
|
||||
type CreateFolderV1Request struct {
|
||||
FolderName string `json:"folderName"`
|
||||
FolderName string `json:"name"`
|
||||
WorkspaceId string `json:"workspaceId"`
|
||||
Environment string `json:"environment"`
|
||||
Directory string `json:"directory"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type CreateFolderV1Response struct {
|
||||
|
@@ -228,7 +228,9 @@ func secretTemplateFunction(accessToken string, existingEtag string, currentEtag
|
||||
*currentEtag = res.Etag
|
||||
}
|
||||
|
||||
return res.Secrets, nil
|
||||
expandedSecrets := util.ExpandSecrets(res.Secrets, models.ExpandSecretsAuthentication{UniversalAuthAccessToken: accessToken}, "")
|
||||
|
||||
return expandedSecrets, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +624,7 @@ var agentCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if !FileExists(configPath) && agentConfigInBase64 == "" {
|
||||
log.Error().Msgf("No agent config file provided. Please provide a agent config file", configPath)
|
||||
log.Error().Msgf("No agent config file provided at %v. Please provide a agent config file", configPath)
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -87,7 +87,9 @@ var exportCmd = &cobra.Command{
|
||||
|
||||
var output string
|
||||
if shouldExpandSecrets {
|
||||
secrets = util.ExpandSecrets(secrets, infisicalToken, "")
|
||||
secrets = util.ExpandSecrets(secrets, models.ExpandSecretsAuthentication{
|
||||
InfisicalToken: infisicalToken,
|
||||
}, "")
|
||||
}
|
||||
secrets = util.FilterSecretsByTag(secrets, tagSlugs)
|
||||
output, err = formatEnvs(secrets, format)
|
||||
|
@@ -110,7 +110,9 @@ var runCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if shouldExpandSecrets {
|
||||
secrets = util.ExpandSecrets(secrets, infisicalToken, projectConfigDir)
|
||||
secrets = util.ExpandSecrets(secrets, models.ExpandSecretsAuthentication{
|
||||
InfisicalToken: infisicalToken,
|
||||
}, projectConfigDir)
|
||||
}
|
||||
|
||||
secretsByKey := getSecretsByKeys(secrets)
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -39,6 +40,11 @@ var secretsCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
infisicalToken, err := cmd.Flags().GetString("token")
|
||||
|
||||
if infisicalToken == "" {
|
||||
infisicalToken = os.Getenv(util.INFISICAL_TOKEN_NAME)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
@@ -80,7 +86,9 @@ var secretsCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if shouldExpandSecrets {
|
||||
secrets = util.ExpandSecrets(secrets, infisicalToken, "")
|
||||
secrets = util.ExpandSecrets(secrets, models.ExpandSecretsAuthentication{
|
||||
InfisicalToken: infisicalToken,
|
||||
}, "")
|
||||
}
|
||||
|
||||
visualize.PrintAllSecretDetails(secrets)
|
||||
@@ -406,6 +414,11 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
|
||||
util.HandleError(err, "Unable to parse path flag")
|
||||
}
|
||||
|
||||
showOnlyValue, err := cmd.Flags().GetBool("raw-value")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse path flag")
|
||||
}
|
||||
|
||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath}, "")
|
||||
if err != nil {
|
||||
util.HandleError(err, "To fetch all secrets")
|
||||
@@ -427,7 +440,15 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
visualize.PrintAllSecretDetails(requestedSecrets)
|
||||
if showOnlyValue && len(requestedSecrets) > 1 {
|
||||
util.PrintErrorMessageAndExit("--raw-value only works with one secret.")
|
||||
}
|
||||
|
||||
if showOnlyValue {
|
||||
fmt.Printf(requestedSecrets[0].Value)
|
||||
} else {
|
||||
visualize.PrintAllSecretDetails(requestedSecrets)
|
||||
}
|
||||
Telemetry.CaptureEvent("cli-command:secrets get", posthog.NewProperties().Set("secretCount", len(secrets)).Set("version", util.CLI_VERSION))
|
||||
}
|
||||
|
||||
@@ -661,6 +682,7 @@ func init() {
|
||||
secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||
secretsCmd.AddCommand(secretsGetCmd)
|
||||
secretsGetCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||
secretsGetCmd.Flags().Bool("raw-value", false, "Returns only the value of secret, only works with one secret")
|
||||
|
||||
secretsCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
||||
secretsCmd.AddCommand(secretsSetCmd)
|
||||
|
@@ -21,11 +21,12 @@ type LoggedInUser struct {
|
||||
}
|
||||
|
||||
type SingleEnvironmentVariable struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type"`
|
||||
ID string `json:"_id"`
|
||||
Tags []struct {
|
||||
Key string `json:"key"`
|
||||
WorkspaceId string `json:"workspace"`
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type"`
|
||||
ID string `json:"_id"`
|
||||
Tags []struct {
|
||||
ID string `json:"_id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
@@ -68,6 +69,7 @@ type GetAllSecretsParameters struct {
|
||||
Environment string
|
||||
EnvironmentPassedViaFlag bool
|
||||
InfisicalToken string
|
||||
UniversalAuthAccessToken string
|
||||
TagSlugs string
|
||||
WorkspaceId string
|
||||
SecretsPath string
|
||||
@@ -96,3 +98,8 @@ type DeleteFolderParameters struct {
|
||||
FolderPath string
|
||||
InfisicalToken string
|
||||
}
|
||||
|
||||
type ExpandSecretsAuthentication struct {
|
||||
InfisicalToken string
|
||||
UniversalAuthAccessToken string
|
||||
}
|
||||
|
@@ -154,7 +154,7 @@ func CreateFolder(params models.CreateFolderParameters) (models.SingleFolder, er
|
||||
WorkspaceId: params.WorkspaceId,
|
||||
Environment: params.Environment,
|
||||
FolderName: params.FolderName,
|
||||
Directory: params.FolderPath,
|
||||
Path: params.FolderPath,
|
||||
}
|
||||
|
||||
apiResponse, err := api.CallCreateFolderV1(httpClient, createFolderRequest)
|
||||
|
@@ -179,7 +179,7 @@ func GetPlainTextSecretsViaMachineIdentity(accessToken string, workspaceId strin
|
||||
}
|
||||
|
||||
for _, secret := range rawSecrets.Secrets {
|
||||
plainTextSecrets = append(plainTextSecrets, models.SingleEnvironmentVariable{Key: secret.SecretKey, Value: secret.SecretValue})
|
||||
plainTextSecrets = append(plainTextSecrets, models.SingleEnvironmentVariable{Key: secret.SecretKey, Value: secret.SecretValue, WorkspaceId: secret.Workspace})
|
||||
}
|
||||
|
||||
// if includeImports {
|
||||
@@ -248,11 +248,8 @@ func FilterSecretsByTag(plainTextSecrets []models.SingleEnvironmentVariable, tag
|
||||
}
|
||||
|
||||
func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectConfigFilePath string) ([]models.SingleEnvironmentVariable, error) {
|
||||
var infisicalToken string
|
||||
if params.InfisicalToken == "" {
|
||||
infisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
|
||||
} else {
|
||||
infisicalToken = params.InfisicalToken
|
||||
params.InfisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
|
||||
}
|
||||
|
||||
isConnected := CheckIsConnectedToInternet()
|
||||
@@ -260,7 +257,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
|
||||
// var serviceTokenDetails api.GetServiceTokenDetailsResponse
|
||||
var errorToReturn error
|
||||
|
||||
if infisicalToken == "" {
|
||||
if params.InfisicalToken == "" && params.UniversalAuthAccessToken == "" {
|
||||
if isConnected {
|
||||
log.Debug().Msg("GetAllEnvironmentVariables: Connected to internet, checking logged in creds")
|
||||
|
||||
@@ -306,12 +303,6 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
|
||||
infisicalDotJson.WorkspaceId = params.WorkspaceId
|
||||
}
|
||||
|
||||
// // Verify environment
|
||||
// err = ValidateEnvironmentName(params.Environment, workspaceFile.WorkspaceId, loggedInUserDetails.UserCredentials)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("unable to validate environment name because [err=%s]", err)
|
||||
// }
|
||||
|
||||
secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, infisicalDotJson.WorkspaceId,
|
||||
params.Environment, params.TagSlugs, params.SecretsPath, params.IncludeImport)
|
||||
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn)
|
||||
@@ -332,91 +323,19 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Debug().Msg("Trying to fetch secrets using service token")
|
||||
secretsToReturn, _, errorToReturn = GetPlainTextSecretsViaServiceToken(infisicalToken, params.Environment, params.SecretsPath, params.IncludeImport)
|
||||
}
|
||||
if params.InfisicalToken != "" {
|
||||
log.Debug().Msg("Trying to fetch secrets using service token")
|
||||
secretsToReturn, _, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport)
|
||||
} else if params.UniversalAuthAccessToken != "" {
|
||||
log.Debug().Msg("Trying to fetch secrets using universal auth")
|
||||
res, err := GetPlainTextSecretsViaMachineIdentity(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport)
|
||||
|
||||
return secretsToReturn, errorToReturn
|
||||
}
|
||||
|
||||
// func ValidateEnvironmentName(environmentName string, workspaceId string, userLoggedInDetails models.UserCredentials) error {
|
||||
// httpClient := resty.New()
|
||||
// httpClient.SetAuthToken(userLoggedInDetails.JTWToken).
|
||||
// SetHeader("Accept", "application/json")
|
||||
|
||||
// response, err := api.CallGetAccessibleEnvironments(httpClient, api.GetAccessibleEnvironmentsRequest{WorkspaceId: workspaceId})
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// listOfEnvSlugs := []string{}
|
||||
// mapOfEnvSlugs := make(map[string]interface{})
|
||||
|
||||
// for _, environment := range response.AccessibleEnvironments {
|
||||
// listOfEnvSlugs = append(listOfEnvSlugs, environment.Slug)
|
||||
// mapOfEnvSlugs[environment.Slug] = environment
|
||||
// }
|
||||
|
||||
// _, exists := mapOfEnvSlugs[environmentName]
|
||||
// if !exists {
|
||||
// HandleError(fmt.Errorf("the environment [%s] does not exist in project with [id=%s]. Only [%s] are available", environmentName, workspaceId, strings.Join(listOfEnvSlugs, ",")))
|
||||
// }
|
||||
|
||||
// return nil
|
||||
|
||||
// }
|
||||
|
||||
func getExpandedEnvVariable(secrets []models.SingleEnvironmentVariable, variableWeAreLookingFor string, hashMapOfCompleteVariables map[string]string, hashMapOfSelfRefs map[string]string) string {
|
||||
if value, found := hashMapOfCompleteVariables[variableWeAreLookingFor]; found {
|
||||
return value
|
||||
}
|
||||
|
||||
for _, secret := range secrets {
|
||||
if secret.Key == variableWeAreLookingFor {
|
||||
regex := regexp.MustCompile(`\${([^\}]*)}`)
|
||||
variablesToPopulate := regex.FindAllString(secret.Value, -1)
|
||||
|
||||
// case: variable is a constant so return its value
|
||||
if len(variablesToPopulate) == 0 {
|
||||
return secret.Value
|
||||
}
|
||||
|
||||
valueToEdit := secret.Value
|
||||
for _, variableWithSign := range variablesToPopulate {
|
||||
variableWithoutSign := strings.Trim(variableWithSign, "}")
|
||||
variableWithoutSign = strings.Trim(variableWithoutSign, "${")
|
||||
|
||||
// case: reference to self
|
||||
if variableWithoutSign == secret.Key {
|
||||
hashMapOfSelfRefs[variableWithoutSign] = variableWithoutSign
|
||||
continue
|
||||
} else {
|
||||
var expandedVariableValue string
|
||||
|
||||
if preComputedVariable, found := hashMapOfCompleteVariables[variableWithoutSign]; found {
|
||||
expandedVariableValue = preComputedVariable
|
||||
} else {
|
||||
expandedVariableValue = getExpandedEnvVariable(secrets, variableWithoutSign, hashMapOfCompleteVariables, hashMapOfSelfRefs)
|
||||
hashMapOfCompleteVariables[variableWithoutSign] = expandedVariableValue
|
||||
}
|
||||
|
||||
// If after expanding all the vars above, is the current var a self ref? if so no replacement needed for it
|
||||
if _, found := hashMapOfSelfRefs[variableWithoutSign]; found {
|
||||
continue
|
||||
} else {
|
||||
valueToEdit = strings.ReplaceAll(valueToEdit, variableWithSign, expandedVariableValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valueToEdit
|
||||
|
||||
} else {
|
||||
continue
|
||||
errorToReturn = err
|
||||
secretsToReturn = res.Secrets
|
||||
}
|
||||
}
|
||||
|
||||
return "${" + variableWeAreLookingFor + "}"
|
||||
return secretsToReturn, errorToReturn
|
||||
}
|
||||
|
||||
var secRefRegex = regexp.MustCompile(`\${([^\}]*)}`)
|
||||
@@ -428,7 +347,7 @@ func recursivelyExpandSecret(expandedSecs map[string]string, interpolatedSecs ma
|
||||
|
||||
interpolatedVal, ok := interpolatedSecs[key]
|
||||
if !ok {
|
||||
HandleError(fmt.Errorf("Could not find refered secret - %s", key), "Kindly check whether its provided")
|
||||
HandleError(fmt.Errorf("could not find refered secret - %s", key), "Kindly check whether its provided")
|
||||
}
|
||||
|
||||
refs := secRefRegex.FindAllStringSubmatch(interpolatedVal, -1)
|
||||
@@ -467,7 +386,7 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod
|
||||
return secretMapByName
|
||||
}
|
||||
|
||||
func ExpandSecrets(secrets []models.SingleEnvironmentVariable, infisicalToken string, projectConfigPathDir string) []models.SingleEnvironmentVariable {
|
||||
func ExpandSecrets(secrets []models.SingleEnvironmentVariable, auth models.ExpandSecretsAuthentication, projectConfigPathDir string) []models.SingleEnvironmentVariable {
|
||||
expandedSecs := make(map[string]string)
|
||||
interpolatedSecs := make(map[string]string)
|
||||
// map[env.secret-path][keyname]Secret
|
||||
@@ -499,8 +418,18 @@ func ExpandSecrets(secrets []models.SingleEnvironmentVariable, infisicalToken st
|
||||
uniqKey := fmt.Sprintf("%s.%s", env, secPathDot)
|
||||
|
||||
if crossRefSec, ok := crossEnvRefSecs[uniqKey]; !ok {
|
||||
|
||||
var refSecs []models.SingleEnvironmentVariable
|
||||
var err error
|
||||
|
||||
// if not in cross reference cache, fetch it from server
|
||||
refSecs, err := GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: env, InfisicalToken: infisicalToken, SecretsPath: secPath}, projectConfigPathDir)
|
||||
if auth.InfisicalToken != "" {
|
||||
refSecs, err = GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: env, InfisicalToken: auth.InfisicalToken, SecretsPath: secPath}, projectConfigPathDir)
|
||||
} else if auth.UniversalAuthAccessToken != "" {
|
||||
refSecs, err = GetAllEnvironmentVariables((models.GetAllSecretsParameters{Environment: env, UniversalAuthAccessToken: auth.UniversalAuthAccessToken, SecretsPath: secPath, WorkspaceId: sec.WorkspaceId}), projectConfigPathDir)
|
||||
} else {
|
||||
HandleError(errors.New("no authentication provided"), "Please provide authentication to fetch secrets")
|
||||
}
|
||||
if err != nil {
|
||||
HandleError(err, fmt.Sprintf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath), "If you are using a service token to fetch secrets, please ensure it is valid")
|
||||
}
|
||||
@@ -508,6 +437,7 @@ func ExpandSecrets(secrets []models.SingleEnvironmentVariable, infisicalToken st
|
||||
// save it to avoid calling api again for same environment and folder path
|
||||
crossEnvRefSecs[uniqKey] = refSecsByKey
|
||||
return refSecsByKey[secKey].Value
|
||||
|
||||
} else {
|
||||
return crossRefSec[secKey].Value
|
||||
}
|
||||
|
@@ -8,9 +8,10 @@ infisical secrets
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
This command enables you to perform CRUD (create, read, update, delete) operations on secrets within your Infisical project. With it, you can view, create, update, and delete secrets in your environment.
|
||||
|
||||
### Sub-commands
|
||||
### Sub-commands
|
||||
<Accordion title="infisical secrets" defaultOpen="true">
|
||||
Use this command to print out all of the secrets in your project
|
||||
|
||||
@@ -18,14 +19,16 @@ This command enables you to perform CRUD (create, read, update, delete) operatio
|
||||
$ infisical secrets
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
### Environment variables
|
||||
|
||||
<Accordion title="INFISICAL_TOKEN">
|
||||
Used to fetch secrets via a [service token](/documentation/platform/token) apposed to logged in credentials. Simply, export this variable in the terminal before running this command.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
# Example
|
||||
export INFISICAL_TOKEN=st.63e03c4a97cb4a747186c71e.ed5b46a34c078a8f94e8228f4ab0ff97.4f7f38034811995997d72badf44b42ec
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="INFISICAL_DISABLE_UPDATE_CHECK">
|
||||
@@ -34,22 +37,26 @@ This command enables you to perform CRUD (create, read, update, delete) operatio
|
||||
To use, simply export this variable in the terminal before running this command.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
# Example
|
||||
export INFISICAL_DISABLE_UPDATE_CHECK=true
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Flags
|
||||
### Flags
|
||||
|
||||
<Accordion title="--expand">
|
||||
Parse shell parameter expansions in your secrets
|
||||
|
||||
Default value: `true`
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--env">
|
||||
Used to select the environment name on which actions should be taken on
|
||||
|
||||
Default value: `dev`
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="--path">
|
||||
The `--path` flag indicates which project folder secrets will be injected from.
|
||||
@@ -58,6 +65,7 @@ This command enables you to perform CRUD (create, read, update, delete) operatio
|
||||
# Example
|
||||
infisical secrets --path="/" --env=dev
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
@@ -65,38 +73,55 @@ This command enables you to perform CRUD (create, read, update, delete) operatio
|
||||
<Accordion title="infisical secrets get">
|
||||
This command allows you selectively print the requested secrets by name
|
||||
|
||||
```bash
|
||||
$ infisical secrets get <secret-name-a> <secret-name-b> ...
|
||||
```bash
|
||||
$ infisical secrets get <secret-name-a> <secret-name-b> ...
|
||||
|
||||
# Example
|
||||
$ infisical secrets get DOMAIN
|
||||
# Example
|
||||
$ infisical secrets get DOMAIN
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
### Flags
|
||||
|
||||
### Flags
|
||||
<Accordion title="--env">
|
||||
Used to select the environment name on which actions should be taken on
|
||||
|
||||
Default value: `dev`
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--raw-value">
|
||||
Used to print the plain value of a single requested secret without any table style.
|
||||
|
||||
Default value: `false`
|
||||
|
||||
Example: `infisical secrets get DOMAIN --value`
|
||||
|
||||
<Tip>
|
||||
When running in CI/CD environments or in a script, set `INFISICAL_DISABLE_UPDATE_CHECK` env to `true`. This will help hide any CLI update messages and only show the secret value.
|
||||
</Tip>
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="infisical secrets set">
|
||||
This command allows you to set or update secrets in your environment. If the secret key provided already exists, its value will be updated with the new value.
|
||||
This command allows you to set or update secrets in your environment. If the secret key provided already exists, its value will be updated with the new value.
|
||||
If the secret key does not exist, a new secret will be created using both the key and value provided.
|
||||
|
||||
```bash
|
||||
$ infisical secrets set <key1=value1> <key2=value2>...
|
||||
|
||||
## Example
|
||||
## Example
|
||||
$ infisical secrets set STRIPE_API_KEY=sjdgwkeudyjwe DOMAIN=example.com HASH=jebhfbwe
|
||||
```
|
||||
|
||||
### Flags
|
||||
### Flags
|
||||
|
||||
<Accordion title="--env">
|
||||
Used to select the environment name on which actions should be taken on
|
||||
|
||||
Default value: `dev`
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="--path">
|
||||
Used to select the project folder in which the secrets will be set. This is useful when creating new secrets under a particular path.
|
||||
@@ -105,43 +130,48 @@ $ infisical secrets set STRIPE_API_KEY=sjdgwkeudyjwe DOMAIN=example.com HASH=jeb
|
||||
# Example
|
||||
infisical secrets set DOMAIN=example.com --path="common/backend"
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="infisical secrets delete">
|
||||
This command allows you to delete secrets by their name(s).
|
||||
|
||||
```bash
|
||||
$ infisical secrets delete <keyName1> <keyName2>...
|
||||
```bash
|
||||
$ infisical secrets delete <keyName1> <keyName2>...
|
||||
|
||||
## Example
|
||||
$ infisical secrets delete STRIPE_API_KEY DOMAIN HASH
|
||||
```
|
||||
## Example
|
||||
$ infisical secrets delete STRIPE_API_KEY DOMAIN HASH
|
||||
```
|
||||
|
||||
### Flags
|
||||
|
||||
### Flags
|
||||
<Accordion title="--env">
|
||||
Used to select the environment name on which actions should be taken on
|
||||
|
||||
Default value: `dev`
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="--path">
|
||||
The `--path` flag indicates which project folder secrets will be injected from.
|
||||
The `--path` flag indicates which project folder secrets will be injected from.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
infisical secrets delete <keyName1> <keyName2>... --path="/"
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="infisical secrets folders">
|
||||
This command allows you to fetch, create and delete folders from within a path from a given project.
|
||||
|
||||
```bash
|
||||
$ infisical secrets folders
|
||||
```
|
||||
```bash
|
||||
$ infisical secrets folders
|
||||
```
|
||||
|
||||
### sub commands
|
||||
|
||||
### sub commands
|
||||
<Accordion title="get">
|
||||
Used to fetch all folders within a path in a given project
|
||||
```
|
||||
@@ -179,6 +209,7 @@ $ infisical secrets set STRIPE_API_KEY=sjdgwkeudyjwe DOMAIN=example.com HASH=jeb
|
||||
|
||||
Default value: ``
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="delete">
|
||||
@@ -194,10 +225,11 @@ $ infisical secrets set STRIPE_API_KEY=sjdgwkeudyjwe DOMAIN=example.com HASH=jeb
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--name">
|
||||
Name of the folder to be deleted within selected `--path`
|
||||
Name of the folder to be deleted within selected `--path`
|
||||
|
||||
Default value: ``
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
@@ -210,14 +242,16 @@ To place default values in your example .env file, you can simply include the sy
|
||||
```bash
|
||||
$ infisical secrets generate-example-env
|
||||
|
||||
## Example
|
||||
## Example
|
||||
$ infisical secrets generate-example-env > .example-env
|
||||
```
|
||||
|
||||
### Flags
|
||||
### Flags
|
||||
|
||||
<Accordion title="--env">
|
||||
Used to select the environment name on which actions should be taken on
|
||||
|
||||
Default value: `dev`
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
@@ -10,7 +10,7 @@ This means that updating the value of a base secret propagates directly to other
|
||||
|
||||
<Note>
|
||||
Currently, the secret referencing feature is only supported by the
|
||||
[Infisical CLI](/cli/overview) and [native integrations](/integrations/overview).
|
||||
[Infisical CLI](/cli/overview), [native integrations](/integrations/overview) and [Infisical Agent](/infisical-agent/overview).
|
||||
|
||||
We intend to add support for it to the [Node SDK](https://infisical.com/docs/sdks/languages/node),
|
||||
[Python SDK](https://infisical.com/docs/sdks/languages/python), and [Java SDK](https://infisical.com/docs/sdks/languages/java) this quarter.
|
||||
|
BIN
docs/images/docker-swarm-secrets-complete.png
Normal file
BIN
docs/images/docker-swarm-secrets-complete.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
164
docs/infisical-agent/guides/docker-swarm-with-agent.mdx
Normal file
164
docs/infisical-agent/guides/docker-swarm-with-agent.mdx
Normal file
@@ -0,0 +1,164 @@
|
||||
---
|
||||
title: 'Docker Swarm'
|
||||
description: "How to manage secrets in Docker Swarm services"
|
||||
---
|
||||
|
||||
In this guide, we'll demonstrate how to use Infisical for managing secrets within Docker Swarm.
|
||||
Specifically, we'll set up a sidecar container using the [Infisical Agent](/infisical-agent/overview), which authenticates with Infisical to retrieve secrets and access tokens.
|
||||
These secrets are then stored in a shared volume accessible by other services in your Docker Swarm.
|
||||
|
||||
## Prerequisites
|
||||
- Infisical account
|
||||
- Docker version 20.10.24 or newer
|
||||
- Basic knowledge of Docker Swarm
|
||||
- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) installed on your system
|
||||
- Familiarity with the [Infisical Agent](/infisical-agent/overview)
|
||||
|
||||
## Objective
|
||||
Our goal is to deploy an Nginx instance in your Docker Swarm cluster, configured to display Infisical secrets on its landing page. This will provide hands-on experience in fetching and utilizing secrets from Infisical within Docker Swarm. The principles demonstrated here are also applicable to Docker Compose deployments.
|
||||
|
||||
<Steps>
|
||||
<Step title="Cloning the Guide Assets Repository">
|
||||
Start by cloning the [Infisical guide assets repository](https://github.com/Infisical/infisical-guides.git) from Github. This repository includes necessary assets for this and other Infisical guides. Focus on the `docker-swarm-with-agent` sub-directory, which we'll use as our working directory.
|
||||
</Step>
|
||||
|
||||
<Step title="Setting Up Authentication with Infisical">
|
||||
To allow the agent to fetch your Infisical secrets, choose an authentication method for the agent. For this guide, we will use [Universal Auth](/documentation/platform/identities/universal-auth) for authentication. Follow the instructions [here](/documentation/platform/identities/universal-auth) to generate a client ID and client secret.
|
||||
</Step>
|
||||
|
||||
<Step title="Entering Universal Auth Credentials">
|
||||
Copy the client ID and client secret obtained in the previous step into the `client-id` and `client-secret` text files, respectively.
|
||||
</Step>
|
||||
|
||||
<Step title="Configuring the Infisical Agent">
|
||||
The Infisical Agent will authenticate using Universal Auth and retrieve secrets for rendering as specified in the template(s).
|
||||
Adjust the `polling-interval` to control the frequency of secret updates.
|
||||
|
||||
In the example template, the secrets are rendered as an HTML page, which will be set as Nginx's home page to demonstrate successful secret retrieval and utilization.
|
||||
|
||||
<Tip>
|
||||
Remember to add your project id, environment slug and path of corresponding Infisical project to the secret template.
|
||||
</Tip>
|
||||
<Tabs>
|
||||
<Tab title="Agent Configuration">
|
||||
```yaml infisical-agent-config
|
||||
infisical:
|
||||
address: "https://app.infisical.com"
|
||||
auth:
|
||||
type: "universal-auth"
|
||||
config:
|
||||
client-id: "/run/secrets/infisical-universal-auth-client-id"
|
||||
client-secret: "/run/secrets/infisical-universal-auth-client-secret"
|
||||
remove_client_secret_on_read: false
|
||||
sinks:
|
||||
- type: "file"
|
||||
config:
|
||||
path: "/infisical-secrets/access-token"
|
||||
templates:
|
||||
- source-path: /run/secrets/nginx-home-page-template
|
||||
destination-path: /infisical-secrets/index.html
|
||||
config:
|
||||
polling-interval: 60s
|
||||
```
|
||||
<Info>
|
||||
Some paths contain `/run/secrets/` because the contents of those files reside in a [Docker secret](https://docs.docker.com/engine/swarm/secrets/#how-docker-manages-secrets).
|
||||
</Info>
|
||||
</Tab>
|
||||
<Tab title="Secret Template for Agent">
|
||||
```html nginx-home-page-template
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<h1>This file is rendered by Infisical agent template engine</h1>
|
||||
<p>Here are the secrets that have been fetched from Infisical and stored in your volume mount</p>
|
||||
<ol>
|
||||
{{- with secret "7df67a5f-d26a-4988-a375-7153c08149da" "dev" "/" }}
|
||||
{{- range . }}
|
||||
<li>{{ .Key }}={{ .Value }}</li>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
</ol>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
|
||||
<Step title="Creating the Docker Compose File">
|
||||
Define the `infisical-agent` and `nginx` services in your Docker Compose file. `infisical-agent` will handle secret retrieval and storage. These secrets are stored in a volume, accessible by other services like Nginx.
|
||||
|
||||
```yaml docker-compose.yaml
|
||||
version: "3.1"
|
||||
|
||||
services:
|
||||
infisical-agent:
|
||||
container_name: infisical-agnet
|
||||
image: infisical/cli:0.18.0
|
||||
command: agent --config=/run/secrets/infisical-agent-config
|
||||
volumes:
|
||||
- infisical-agent:/infisical-secrets
|
||||
secrets:
|
||||
- infisical-universal-auth-client-id
|
||||
- infisical-universal-auth-client-secret
|
||||
- infisical-agent-config
|
||||
- nginx-home-page-template
|
||||
networks:
|
||||
- infisical_network
|
||||
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- infisical-agent:/usr/share/nginx/html
|
||||
networks:
|
||||
- infisical_network
|
||||
|
||||
volumes:
|
||||
infisical-agent:
|
||||
|
||||
secrets:
|
||||
infisical-universal-auth-client-id:
|
||||
file: ./client-id
|
||||
infisical-universal-auth-client-secret:
|
||||
file: ./client-secret
|
||||
infisical-agent-config:
|
||||
file: ./infisical-agent-config
|
||||
nginx-home-page-template:
|
||||
file: ./nginx-home-page-template
|
||||
|
||||
|
||||
networks:
|
||||
infisical_network:
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Initializing Docker Swarm">
|
||||
```
|
||||
docker swarm init
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Deploying the Docker Stack">
|
||||
```
|
||||
docker stack deploy -c docker-compose.yaml agent-demo
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Verifying Secret Consumption">
|
||||
To confirm that secrets are properly rendered and accessible, navigate to `http://localhost`. You should see the Infisical secrets displayed on the Nginx landing page.
|
||||
|
||||

|
||||
</Step>
|
||||
|
||||
<Step title="Clean up">
|
||||
```
|
||||
docker stack rm agent-demo
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Considerations
|
||||
- Secret Updates: Applications that access secrets directly from the volume mount will receive updates in real-time, in accordance with the `polling-interval` set in agent config.
|
||||
- In-Memory Secrets: If your application loads secrets into memory, the new secrets will be available to the application on the next deployment.
|
@@ -228,12 +228,27 @@
|
||||
{
|
||||
"group": "Agent",
|
||||
"pages": [
|
||||
"infisical-agent/overview"
|
||||
"infisical-agent/overview",
|
||||
{
|
||||
"group": "Use cases",
|
||||
"pages": [
|
||||
"infisical-agent/guides/docker-swarm-with-agent",
|
||||
"integrations/platforms/ecs-with-agent"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Infrastructure Integrations",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Container orchestrators",
|
||||
"pages": [
|
||||
"integrations/platforms/kubernetes",
|
||||
"infisical-agent/guides/docker-swarm-with-agent",
|
||||
"integrations/platforms/ecs-with-agent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Docker",
|
||||
"pages": [
|
||||
@@ -243,10 +258,8 @@
|
||||
"integrations/platforms/docker-compose"
|
||||
]
|
||||
},
|
||||
"integrations/platforms/kubernetes",
|
||||
"integrations/frameworks/terraform",
|
||||
"integrations/platforms/ansible",
|
||||
"integrations/platforms/ecs-with-agent"
|
||||
"integrations/platforms/ansible"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@@ -74,16 +74,6 @@ export default function RailwayCreateIntegrationPage() {
|
||||
}
|
||||
}, [targetEnvironments]);
|
||||
|
||||
useEffect(() => {
|
||||
if (targetServices) {
|
||||
if (targetServices.length > 0) {
|
||||
setTargetServiceId(targetServices[0].serviceId);
|
||||
} else {
|
||||
setTargetServiceId("none");
|
||||
}
|
||||
}
|
||||
}, [targetServices]);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -124,11 +114,14 @@ export default function RailwayCreateIntegrationPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const filteredTargetServices = targetServices ? [ { name: "", serviceId: "none" }, ...targetServices ] : [ { name: "", serviceId: "none" } ];
|
||||
|
||||
return workspace &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetEnvironments &&
|
||||
targetServices ? (
|
||||
targetServices &&
|
||||
filteredTargetServices ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Railway Integration</CardTitle>
|
||||
@@ -208,20 +201,14 @@ export default function RailwayCreateIntegrationPage() {
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={targetServices.length === 0}
|
||||
>
|
||||
{targetServices.length > 0 ? (
|
||||
targetServices.map((targetService) => (
|
||||
{filteredTargetServices.map((targetService) => (
|
||||
<SelectItem
|
||||
value={targetService.serviceId as string}
|
||||
key={`target-service-${targetService.serviceId as string}`}
|
||||
>
|
||||
{targetService.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-service-none">
|
||||
No services found
|
||||
</SelectItem>
|
||||
)}
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
|
@@ -75,7 +75,7 @@ export const CreateSecretForm = ({
|
||||
const isEnvironmentsSelected = environmentsSelected.length;
|
||||
|
||||
if (!isEnvironmentsSelected) {
|
||||
createNotification({ type: "error", text: "Select atleast one environment" });
|
||||
createNotification({ type: "error", text: "Select at least one environment" });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -143,8 +143,8 @@ export const CreateSecretForm = ({
|
||||
<Modal isOpen={isOpen} onOpenChange={onTogglePopUp}>
|
||||
<ModalContent
|
||||
className="max-h-[80vh] overflow-y-auto"
|
||||
title="Add Secrets"
|
||||
subTitle="Add secrets in all environments at once."
|
||||
title="Bulk Create & Update"
|
||||
subTitle="Create & update a secret across many environments"
|
||||
>
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||
<FormControl label="Key" isError={Boolean(errors?.key)} errorText={errors?.key?.message}>
|
||||
|
@@ -0,0 +1,52 @@
|
||||
import { useCallback } from "react";
|
||||
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { Button } from "@app/components/v2";
|
||||
import { useToggle } from "@app/hooks";
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
hoverText: string;
|
||||
notificationText: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const CopyButton = ({ value, children, hoverText, notificationText }: Props) => {
|
||||
const [isProjectIdCopied, setIsProjectIdCopied] = useToggle(false);
|
||||
const { createNotification } = useNotificationContext();
|
||||
|
||||
const copyToClipboard = useCallback(() => {
|
||||
if (isProjectIdCopied) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsProjectIdCopied.on();
|
||||
navigator.clipboard.writeText(value);
|
||||
|
||||
createNotification({
|
||||
text: notificationText,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
const timer = setTimeout(() => setIsProjectIdCopied.off(), 2000);
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return () => clearTimeout(timer);
|
||||
}, [isProjectIdCopied]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
className="group relative"
|
||||
leftIcon={<FontAwesomeIcon icon={isProjectIdCopied ? faCheck : faCopy} />}
|
||||
onClick={copyToClipboard}
|
||||
>
|
||||
{children}
|
||||
<span className="absolute -left-8 -top-20 hidden translate-y-full justify-center rounded-md bg-bunker-800 py-2 px-3 text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||
{hoverText}
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
@@ -1,7 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
@@ -9,9 +7,10 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, FormControl, Input } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { useRenameWorkspace } from "@app/hooks/api";
|
||||
|
||||
import { CopyButton } from "./CopyButton";
|
||||
|
||||
const formSchema = yup.object({
|
||||
name: yup.string().required().label("Project Name")
|
||||
});
|
||||
@@ -22,7 +21,6 @@ export const ProjectNameChangeSection = () => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { mutateAsync, isLoading } = useRenameWorkspace();
|
||||
const [isProjectIdCopied, setIsProjectIdCopied] = useToggle(false);
|
||||
|
||||
const { handleSubmit, control, reset } = useForm<FormData>({ resolver: yupResolver(formSchema) });
|
||||
|
||||
@@ -34,16 +32,6 @@ export const ProjectNameChangeSection = () => {
|
||||
}
|
||||
}, [currentWorkspace]);
|
||||
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
|
||||
if (isProjectIdCopied) {
|
||||
timer = setTimeout(() => setIsProjectIdCopied.off(), 2000);
|
||||
}
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [setIsProjectIdCopied]);
|
||||
|
||||
const onFormSubmit = async ({ name }: FormData) => {
|
||||
try {
|
||||
if (!currentWorkspace?.id) return;
|
||||
@@ -66,35 +54,28 @@ export const ProjectNameChangeSection = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const copyProjectIdToClipboard = () => {
|
||||
navigator.clipboard.writeText(currentWorkspace?.id || "");
|
||||
setIsProjectIdCopied.on();
|
||||
|
||||
createNotification({
|
||||
text: "Copied Project ID to clipboard",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||
>
|
||||
<div className="flex justify-betweens">
|
||||
<h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8">Project Name</h2>
|
||||
<div>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
className="group relative"
|
||||
leftIcon={<FontAwesomeIcon icon={isProjectIdCopied ? faCheck : faCopy} />}
|
||||
onClick={copyProjectIdToClipboard}
|
||||
<div className="justify-betweens flex">
|
||||
<h2 className="mb-8 flex-1 text-xl font-semibold text-mineshaft-100">Project Name</h2>
|
||||
<div className="space-x-2">
|
||||
<CopyButton
|
||||
value={currentWorkspace?.slug || ""}
|
||||
hoverText="Click to project slug"
|
||||
notificationText="Copied project slug to clipboard"
|
||||
>
|
||||
Copy Project Slug
|
||||
</CopyButton>
|
||||
<CopyButton
|
||||
value={currentWorkspace?.id || ""}
|
||||
hoverText="Click to project ID"
|
||||
notificationText="Copied project ID to clipboard"
|
||||
>
|
||||
Copy Project ID
|
||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||
Click to copy
|
||||
</span>
|
||||
</Button>
|
||||
</CopyButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
Reference in New Issue
Block a user