Update package-lock.json

This commit is contained in:
Tuan Dang
2023-04-19 18:18:38 +03:00
25 changed files with 1761 additions and 1133 deletions

2116
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
{ {
"dependencies": { "dependencies": {
"@aws-sdk/client-secrets-manager": "^3.294.0", "@aws-sdk/client-secrets-manager": "^3.299.0",
"@godaddy/terminus": "^4.11.2", "@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5", "@octokit/rest": "^19.0.5",
"@sentry/tracing": "^7.41.0", "@sentry/node": "^7.45.0",
"@sentry/node": "^7.40.0", "@sentry/tracing": "^7.45.0",
"@sentry/node": "^7.41.0", "@sentry/node": "^7.41.0",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10", "@types/libsodium-wrappers": "^0.7.10",
@@ -31,7 +31,7 @@
"jsrp": "^0.2.4", "jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10", "libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mongoose": "^6.10.3", "mongoose": "^6.10.4",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"posthog-node": "^2.6.0", "posthog-node": "^2.6.0",
"query-string": "^7.1.3", "query-string": "^7.1.3",

View File

@@ -74,7 +74,8 @@ import {
getNodeEnv, getNodeEnv,
getPort, getPort,
getSentryDSN, getSentryDSN,
getSiteURL getSiteURL,
getSmtpHost
} from './config'; } from './config';
const main = async () => { const main = async () => {

View File

@@ -44,7 +44,7 @@ router.post(
const secretIds = requests const secretIds = requests
.map((request) => request.secret._id) .map((request) => request.secret._id)
.filter((secretId) => secretId !== undefined) .filter((secretId) => secretId !== undefined)
if (secretIds.length > 0) { if (secretIds.length > 0) {
req.secrets = await validateClientForSecrets({ req.secrets = await validateClientForSecrets({
authData: req.authData, authData: req.authData,
@@ -53,8 +53,8 @@ router.post(
}); });
} }
} }
return true; return true;
}), }),
validateRequest, validateRequest,
secretsController.batchSecrets secretsController.batchSecrets
); );
@@ -123,7 +123,7 @@ router.get(
query('tagSlugs'), query('tagSlugs'),
validateRequest, validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN] acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN, AUTH_MODE_SERVICE_ACCOUNT]
}), }),
requireWorkspaceAuth({ requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER], acceptedRoles: [ADMIN, MEMBER],

View File

@@ -5,11 +5,11 @@ description: "How to authenticate with the Infisical Public API"
## Essentials ## Essentials
The Public API accepts multiple modes of authentication being via API Key, Service Account credentials, or [Infisical Token](../../../getting-started/dashboard/token). The Public API accepts multiple modes of authentication being via API Key, Service Account credentials, or [Infisical Token](../../getting-started/dashboard/token).
- API Key: Provides full access to all endpoints representing the user. - API Key: Provides full access to all endpoints representing the user.
- [Service Account](): Provides scoped access to an organization and select projects representing a machine such as a VM or application client. - [Service Account](): Provides scoped access to an organization and select projects representing a machine such as a VM or application client.
- [Infisical Token](../../../getting-started/dashboard/token): Provides short-lived, scoped CRUD access to the secrets of a specific project and environment. - [Infisical Token](../../getting-started/dashboard/token): Provides short-lived, scoped CRUD access to the secrets of a specific project and environment.
<AccordionGroup> <AccordionGroup>
<Accordion title="API Key"> <Accordion title="API Key">
@@ -48,4 +48,4 @@ Depending on your use case, it may make sense to use one or another authenticati
- API Key (not recommended): Use if you need full access to the Public API without needing to access any secrets endpoints (because API keys can't encrypt/decrypt secrets). - API Key (not recommended): Use if you need full access to the Public API without needing to access any secrets endpoints (because API keys can't encrypt/decrypt secrets).
- Service Account (recommeded): Use if you need access to multiple projects and environments in an organization; service accounts can generate short-lived access tokens, making them useful for some complex setups. - Service Account (recommeded): Use if you need access to multiple projects and environments in an organization; service accounts can generate short-lived access tokens, making them useful for some complex setups.
- Service Token (recommeded): Use if you need short-lived, scoped CRUD access to the secrets of a specific project and environment. - Service Token (recommeded): Use if you need short-lived, scoped CRUD access to the secrets of a specific project and environment.

View File

@@ -38,12 +38,10 @@ The operator can be install via [Helm](helm.sh) or [kubectl](https://github.com/
</Tabs> </Tabs>
## Sync Infisical Secrets to your cluster ## Sync Infisical Secrets to your cluster
To retrieve secrets from an Infisical project and save them as native Kubernetes secrets within a specific namespace, utilize the `InfisicalSecret` custom resource definition (CRD).
To retrieve secrets from an Infisical project and store them in your Kubernetes cluster, you can use the InfisicalSecret custom resource. This resource can be created after installing the Infisical operator. For each new managed secret, you will need to create a new InfisicalSecret CRD.
This resource is available after installing the Infisical operator. In order to specify the Infisical Token location and the location where the retrieved secrets should be stored, you can use the `tokenSecretReference` and `managedSecretReference` fields within the InfisicalSecret resource.
```yaml ```yaml
apiVersion: secrets.infisical.com/v1alpha1 apiVersion: secrets.infisical.com/v1alpha1
kind: InfisicalSecret kind: InfisicalSecret
metadata: metadata:
@@ -52,46 +50,123 @@ metadata:
spec: spec:
# The host that should be used to pull secrets from. If left empty, the value specified in Global configuration will be used # The host that should be used to pull secrets from. If left empty, the value specified in Global configuration will be used
hostAPI: https://app.infisical.com/api hostAPI: https://app.infisical.com/api
authentication:
# The Kubernetes secret the stores the Infisical token serviceToken: # <-- option 1
tokenSecretReference: serviceTokenSecretReference:
# Kubernetes secret name secretName: service-token
secretName: service-token secretNamespace: option
# The secret namespace serviceAccount: # <-- method 2
secretNamespace: default serviceAccountSecretReference:
secretName: service-account
# The Kubernetes secret that Infisical Operator will create and populate with secrets from the above project secretNamespace: default
projectId: "6439ec224cfbf7ea2a95b651"
environmentName: "dev"
managedSecretReference: managedSecretReference:
# The name of managed Kubernetes secret that should be created secretName: managed-secret # <-- the name of kubernetes secret that will be created
secretName: managed-secret secretNamespace: default # <-- where the kubernetes secret that will be created
# The namespace the managed secret should be installed in
secretNamespace: default
``` ```
### InfisicalSecret CRD properties
<Accordion title="tokenSecretReference"> <Accordion title="hostAPI">
The `tokenSecretReference` field in the InfisicalSecret resource is used to specify the location of the Infisical Token, which is required for authenticating and retrieving secrets from an Infisical project. If you are fetching secrets from a self hosted instance of Infisical set the value of `hostAPI` to
` https://your-self-hosted-instace.com/api`
To create a Kubernetes secret containing an [Infisical Token](../../getting-started/dashboard/token), you can run the command below.
``` bash
kubectl create secret generic service-token --from-literal=infisicalToken=<infisical-token-here>
```
Once the secret is created, add the name and namespace of the secret under `tokenSecretReference` field in the InfisicalSecret custom resource. When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
</Accordion>
{' '} <Accordion title="authentication">
The `authentication` property tells the operator where it should look to find credentials needed to fetch secrets from Infisical. You can authenticate via two methods as described below.
<Info> <Tabs>
No matter what the name of the secret is or its namespace, it must contain a <Tab title="Service Token">
key named `infisicalToken` with a valid Infisical Token as the value Authenticating with service tokens is a great option when you have a small number of services you'd like to fetch secrets for and are looking for the least amount of setup.
</Info>
#### 1. Generate service token
You can generate a service token for an Infisical project by heading over to the Infisical dashboard then to Project Settings.
#### 2. Create Kubernetes secret containing service token
Once you have generated the service token, you will need to create a Kubernetes secret containing the service token you generated.
To quickly create a Kubernetes secret containing the generated service token, you can run the command below.
``` bash
kubectl create secret generic service-token --from-literal=infisicalToken=<your-service-token-here>
```
#### 3. Add reference for the Kubernetes secret containing service token
Once the secret is created, add the name and namespace of the secret that was just created under `authentication.serviceToken.serviceTokenSecretReference` field in the InfisicalSecret resource.
## Example
```yaml
apiVersion: secrets.infisical.com/v1alpha1
kind: InfisicalSecret
metadata:
name: infisicalsecret-sample-crd
spec:
authentication:
serviceToken:
serviceTokenSecretReference:
secretName: service-token # <-- name of the Kubernetes secret that stores our service token
secretNamespace: option # <-- namespace of the Kubernetes secret that stores our service token
...
```
</Tab>
<Tab title="Service Account">
We recommend authenticating with service account credentials when you have a large number of services. With this method, instead of creating a service token for each Infisical project you'd like to
fetch secrets from, you can fetch secrets from a number of Infisical projects with just one set of credentials.
#### 1. Generate service account
You can generate a service account by heading over to the organization settings. Once you create the service account, keep the credentials at hand for the next steps.
#### 2. Grant service account access to Infisical projects
Click on the pencil icon on the service account you just created and add the projects you'd like to be accessible via that service account.
#### 3. Store service account credentials in K8 secret
Next, we'll need to store the service account credentials in a kubernetes secret so that we can reference it in our InfisicalSecret CRD.
We recommend you create this kubernetes secret in a new namespace since you may need to reference it many times for each InfisicalSecret CRD you create.
To quickly create a Kubernetes secret containing the service account details, you can execute the command below after replacing it with your own service account credentials.
```
kubectl create secret generic service-token --from-literal=serviceAccountAccessKey=[REPLACE] --from-literal=serviceAccountPrivateKey=[REPLACE] --from-literal=serviceAccountPublicKey=[REPLACE]
```
Regardless of how you create the kubernetes secret containing the service account credentials, you will need to define values for the following keys in the secret: `serviceAccountAccessKey`, `serviceAccountPrivateKey`, and `serviceAccountPublicKey`
Once the secret is created, add the name and namespace of the secret that was just created under `authentication.serviceAccount.serviceAccountSecretReference` field in the InfisicalSecret CRD.
#### 4. Add projectId and environment from which to fetch secrets from
Add the Infisical project id and environment from which to fetch secrets for by providing values under `authentication.serviceAccount.projectId` and `authentication.serviceAccount.environmentName`.
## Example
```yaml
apiVersion: secrets.infisical.com/v1alpha1
kind: InfisicalSecret
metadata:
name: infisicalsecret-sample-crd
spec:
serviceAccount:
serviceAccountSecretReference:
secretName: service-account
secretNamespace: default
projectId: "6439ec224cfbf7ea2a95b651"
environmentName: "dev"
```
</Tab>
</Tabs>
</Accordion> </Accordion>
<Accordion title="managedSecretReference"> <Accordion title="managedSecretReference">
The `managedSecretReference` field in the InfisicalSecret resource is used to specify the location where secrets retrieved from an Infisical project should be stored. The `managedSecretReference` field in the InfisicalSecret resource is used to specify the location where secrets retrieved from an Infisical project should be stored.
You should specify the name and namespace of the Kubernetes secret that will hold these secrets. The operator will create the secret for you, you just need to provide its name and namespace. You should specify the name and namespace of the Kubernetes secret that will hold these secrets. The operator will create the secret for you, you just need to provide its name and namespace.
It is recommended that the managed secret be created in the same namespace as the deployment that will use it. The managed secret be should be created in the same namespace as the deployment that will use it.
</Accordion> </Accordion>

View File

@@ -19,8 +19,8 @@ import {
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tag } from 'public/data/frequentInterfaces'; import { Tag } from 'public/data/frequentInterfaces';
import queryString from 'query-string';
// import queryString from 'query-string';
import Button from '@app/components/basic/buttons/Button'; import Button from '@app/components/basic/buttons/Button';
import BottonRightPopup from '@app/components/basic/popups/BottomRightPopup'; import BottonRightPopup from '@app/components/basic/popups/BottomRightPopup';
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider'; import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
@@ -173,7 +173,7 @@ export default function Dashboard() {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const router = useRouter(); const router = useRouter();
const envInURL = queryString.parse(router.asPath.split('?')[1])?.env; // const envInURL = queryString.parse(router.asPath.split('?')[1])?.env;
const workspaceId = router.query.id as string; const workspaceId = router.query.id as string;
const [workspaceEnvs, setWorkspaceEnvs] = useState<WorkspaceEnv[]>([]); const [workspaceEnvs, setWorkspaceEnvs] = useState<WorkspaceEnv[]>([]);
@@ -240,7 +240,7 @@ export default function Dashboard() {
setWorkspaceEnvs(accessibleEnvironments || []); setWorkspaceEnvs(accessibleEnvironments || []);
// set env // set env
const env = accessibleEnvironments?.[0] || { const env = accessibleEnvironments[0] || {
name: 'unknown', name: 'unknown',
slug: 'unknown' slug: 'unknown'
}; };
@@ -818,7 +818,7 @@ export default function Dashboard() {
}; };
return <div> return <div>
{!envInURL {false
? <DashboardEnvOverview /> ? <DashboardEnvOverview />
: (data ? ( : (data ? (
<div className="bg-bunker-800 max-h-screen h-full relative flex flex-col justify-between text-white dark"> <div className="bg-bunker-800 max-h-screen h-full relative flex flex-col justify-between text-white dark">
@@ -848,7 +848,7 @@ export default function Dashboard() {
<div className="w-full max-h-96 pb-2 dark:[color-scheme:dark]"> <div className="w-full max-h-96 pb-2 dark:[color-scheme:dark]">
<NavHeader <NavHeader
pageName={t('dashboard:title')} pageName={t('dashboard:title')}
currentEnv={workspaceEnvs?.filter(envir => envir.slug === envInURL)[0].name || ''} currentEnv={selectedEnv?.name || ''}
isProjectRelated isProjectRelated
userAvailableEnvs={workspaceEnvs} userAvailableEnvs={workspaceEnvs}
onEnvChange={handleOnEnvironmentChange} onEnvChange={handleOnEnvironmentChange}
@@ -1020,7 +1020,7 @@ export default function Dashboard() {
<div className="flex items-center justify-center h-full my-48"> <div className="flex items-center justify-center h-full my-48">
<Image <Image
src="/images/loading/loading.gif" src="/images/loading/loading.gif"
height={600} height={60}
width={100} width={100}
alt="infisical loading indicator" alt="infisical loading indicator"
/> />

View File

@@ -28,8 +28,8 @@ const DashboardInput = ({ isOverridden, isSecretValueHidden, isReadOnly, secret,
ref.current.scrollLeft = e.currentTarget.scrollLeft; ref.current.scrollLeft = e.currentTarget.scrollLeft;
}; };
return <td key={`row-${secret?.key || ''}--`} className={`flex flex-row w-full min-w-[11rem] justify-center h-10 items-center ${!(secret?.value || secret?.value === '') ? "bg-red-400/10" : "bg-mineshaft-900/30"}`}> return <td key={`row-${secret?.key || ''}--`} className={`flex cursor-default flex-row w-full min-w-[11rem] justify-center h-10 items-center ${!(secret?.value || secret?.value === '') ? "bg-red-400/10" : "bg-mineshaft-900/30"}`}>
<div className="group relative whitespace-pre flex flex-col justify-center w-full"> <div className="group relative whitespace-pre flex flex-col justify-center w-full cursor-default">
<input <input
// {...register(`secrets.${index}.valueOverride`)} // {...register(`secrets.${index}.valueOverride`)}
defaultValue={(isOverridden ? secret.valueOverride : secret?.value || '')} defaultValue={(isOverridden ? secret.valueOverride : secret?.value || '')}
@@ -39,7 +39,7 @@ const DashboardInput = ({ isOverridden, isSecretValueHidden, isReadOnly, secret,
isSecretValueHidden isSecretValueHidden
? 'text-transparent focus:text-transparent active:text-transparent' ? 'text-transparent focus:text-transparent active:text-transparent'
: '' : ''
} z-10 peer font-mono ph-no-capture bg-transparent caret-transparent text-transparent text-sm px-2 py-2 w-full outline-none duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`} } z-10 peer cursor-default font-mono ph-no-capture bg-transparent caret-transparent text-transparent text-sm px-2 py-2 w-full outline-none duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
spellCheck="false" spellCheck="false"
/> />
<div <div
@@ -49,7 +49,7 @@ const DashboardInput = ({ isOverridden, isSecretValueHidden, isReadOnly, secret,
? 'text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-100 peer-active:text-gray-400 duration-200' ? 'text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-100 peer-active:text-gray-400 duration-200'
: '' : ''
} ${!secret?.value && "text-bunker-400 justify-center"} } ${!secret?.value && "text-bunker-400 justify-center"}
absolute flex flex-row whitespace-pre font-mono z-0 ${isSecretValueHidden && secret?.value ? 'invisible' : 'visible'} peer-focus:visible mt-0.5 ph-no-capture overflow-x-scroll bg-transparent h-10 text-sm px-2 py-2 w-full min-w-16 outline-none duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`} absolute cursor-default flex flex-row whitespace-pre font-mono z-0 ${isSecretValueHidden && secret?.value ? 'invisible' : 'visible'} peer-focus:visible mt-0.5 ph-no-capture overflow-x-scroll bg-transparent h-10 text-sm px-2 py-2 w-full min-w-16 outline-none duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
> >
{(secret?.value || secret?.value === '') && (isOverridden ? secret.valueOverride : secret?.value)?.split('').length === 0 && <span className='text-bunker-400/80 font-sans w-full'>EMPTY</span>} {(secret?.value || secret?.value === '') && (isOverridden ? secret.valueOverride : secret?.value)?.split('').length === 0 && <span className='text-bunker-400/80 font-sans w-full'>EMPTY</span>}
{(secret?.value || secret?.value === '') && (isOverridden ? secret.valueOverride : secret?.value)?.split(REGEX).map((word: string) => { {(secret?.value || secret?.value === '') && (isOverridden ? secret.valueOverride : secret?.value)?.split(REGEX).map((word: string) => {
@@ -78,7 +78,7 @@ const DashboardInput = ({ isOverridden, isSecretValueHidden, isReadOnly, secret,
</span> </span>
); );
})} })}
{!(secret?.value || secret?.value === '') && <span className='text-red-500/80 font-sans text-xs italic'>missing</span>} {!(secret?.value || secret?.value === '') && <span className='text-red-500/80 cursor-default font-sans text-xs italic'>missing</span>}
</div> </div>
{(isSecretValueHidden && secret?.value) && ( {(isSecretValueHidden && secret?.value) && (
<div className='absolute flex flex-row justify-between items-center z-0 peer pr-2 peer-active:hidden peer-focus:hidden group-hover:bg-white/[0.00] duration-100 h-10 w-full text-bunker-400 text-clip'> <div className='absolute flex flex-row justify-between items-center z-0 peer pr-2 peer-active:hidden peer-focus:hidden group-hover:bg-white/[0.00] duration-100 h-10 w-full text-bunker-400 text-clip'>
@@ -118,7 +118,7 @@ export const EnvComparisonRow = ({
<tr className="group min-w-full flex flex-row items-center hover:bg-bunker-700"> <tr className="group min-w-full flex flex-row items-center hover:bg-bunker-700">
<td className="w-10 h-10 px-4 flex items-center justify-center border-none"><div className='text-center w-10 text-xs text-bunker-400'>{index + 1}</div></td> <td className="w-10 h-10 px-4 flex items-center justify-center border-none"><div className='text-center w-10 text-xs text-bunker-400'>{index + 1}</div></td>
<td className="flex flex-row justify-between items-center h-full min-w-[200px] lg:min-w-[220px] xl:min-w-[250px]"> <td className="flex flex-row justify-between items-center h-full min-w-[200px] lg:min-w-[220px] xl:min-w-[250px]">
<div className="flex flex-row items-center h-8">{secret?.key || ''}</div> <div className="flex flex-row items-center h-8 cursor-default">{secret?.key || ''}</div>
<button type="button" className='mr-2 text-bunker-400 hover:text-bunker-300 invisible group-hover:visible' onClick={() => setAreValuesHiddenThisRow(!areValuesHiddenThisRow)}> <button type="button" className='mr-2 text-bunker-400 hover:text-bunker-300 invisible group-hover:visible' onClick={() => setAreValuesHiddenThisRow(!areValuesHiddenThisRow)}>
<FontAwesomeIcon icon={areValuesHiddenThisRow ? faEye : faEyeSlash} /> <FontAwesomeIcon icon={areValuesHiddenThisRow ? faEye : faEyeSlash} />
</button> </button>

View File

@@ -62,7 +62,10 @@ export const OrgSettingsPage = () => {
(orgUsers || []).length >= 5 && (orgUsers || []).length >= 5 &&
subscriptionPlan === plans.starter && subscriptionPlan === plans.starter &&
host === 'https://app.infisical.com' && host === 'https://app.infisical.com' &&
currentWorkspace?._id !== '63ea8121b6e2b0543ba79616'; currentWorkspace?._id !== '63ea8121b6e2b0543ba79616' &&
currentWorkspace?._id !== '634870246fd2e26f28e76996' &&
currentWorkspace?._id !== '63d823cef9e728a0a961255a' &&
currentWorkspace?._id !== '6412ec319db25595ac00b8c6';
const onRenameOrg = async (name: string) => { const onRenameOrg = async (name: string) => {
if (!currentOrg?._id) return; if (!currentOrg?._id) return;

View File

@@ -13,9 +13,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.4 version: 0.1.6
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "0.1.4" appVersion: "0.1.6"

View File

@@ -35,6 +35,51 @@ spec:
spec: spec:
description: InfisicalSecretSpec defines the desired state of InfisicalSecret description: InfisicalSecretSpec defines the desired state of InfisicalSecret
properties: properties:
authentication:
properties:
serviceAccount:
properties:
environmentName:
type: string
projectId:
type: string
serviceAccountSecretReference:
properties:
secretName:
description: The name of the Kubernetes Secret
type: string
secretNamespace:
description: The name space where the Kubernetes Secret
is located
type: string
required:
- secretName
- secretNamespace
type: object
required:
- environmentName
- projectId
- serviceAccountSecretReference
type: object
serviceToken:
properties:
serviceTokenSecretReference:
properties:
secretName:
description: The name of the Kubernetes Secret
type: string
secretNamespace:
description: The name space where the Kubernetes Secret
is located
type: string
required:
- secretName
- secretNamespace
type: object
required:
- serviceTokenSecretReference
type: object
type: object
hostAPI: hostAPI:
description: Infisical host to pull secrets from description: Infisical host to pull secrets from
type: string type: string
@@ -62,6 +107,8 @@ spec:
- secretName - secretName
- secretNamespace - secretNamespace
type: object type: object
required:
- managedSecretReference
type: object type: object
status: status:
description: InfisicalSecretStatus defines the observed state of InfisicalSecret description: InfisicalSecretStatus defines the observed state of InfisicalSecret

View File

@@ -4,6 +4,23 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type ServiceTokenDetails struct {
ServiceTokenSecretReference KubeSecretReference `json:"serviceTokenSecretReference"`
}
type ServiceAccountDetails struct {
ServiceAccountSecretReference KubeSecretReference `json:"serviceAccountSecretReference"`
ProjectId string `json:"projectId"`
EnvironmentName string `json:"environmentName"`
}
type Authentication struct {
// +kubebuilder:validation:Optional
ServiceAccount ServiceAccountDetails `json:"serviceAccount"`
// +kubebuilder:validation:Optional
ServiceToken ServiceTokenDetails `json:"serviceToken"`
}
type KubeSecretReference struct { type KubeSecretReference struct {
// The name of the Kubernetes Secret // The name of the Kubernetes Secret
// +kubebuilder:validation:Required // +kubebuilder:validation:Required
@@ -16,13 +33,18 @@ type KubeSecretReference struct {
// InfisicalSecretSpec defines the desired state of InfisicalSecret // InfisicalSecretSpec defines the desired state of InfisicalSecret
type InfisicalSecretSpec struct { type InfisicalSecretSpec struct {
// +kubebuilder:validation:Optional
TokenSecretReference KubeSecretReference `json:"tokenSecretReference"`
// +kubebuilder:validation:Optional
Authentication Authentication `json:"authentication"`
// +kubebuilder:validation:Required // +kubebuilder:validation:Required
TokenSecretReference KubeSecretReference `json:"tokenSecretReference,omitempty"` ManagedSecretReference KubeSecretReference `json:"managedSecretReference"`
// +kubebuilder:validation:Required
ManagedSecretReference KubeSecretReference `json:"managedSecretReference,omitempty"`
// Infisical host to pull secrets from // Infisical host to pull secrets from
HostAPI string `json:"hostAPI,omitempty"` // +kubebuilder:validation:Optional
HostAPI string `json:"hostAPI"`
} }
// InfisicalSecretStatus defines the observed state of InfisicalSecret // InfisicalSecretStatus defines the observed state of InfisicalSecret

View File

@@ -26,6 +26,23 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime" runtime "k8s.io/apimachinery/pkg/runtime"
) )
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Authentication) DeepCopyInto(out *Authentication) {
*out = *in
out.ServiceAccount = in.ServiceAccount
out.ServiceToken = in.ServiceToken
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authentication.
func (in *Authentication) DeepCopy() *Authentication {
if in == nil {
return nil
}
out := new(Authentication)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *InfisicalSecret) DeepCopyInto(out *InfisicalSecret) { func (in *InfisicalSecret) DeepCopyInto(out *InfisicalSecret) {
*out = *in *out = *in
@@ -89,6 +106,7 @@ func (in *InfisicalSecretList) DeepCopyObject() runtime.Object {
func (in *InfisicalSecretSpec) DeepCopyInto(out *InfisicalSecretSpec) { func (in *InfisicalSecretSpec) DeepCopyInto(out *InfisicalSecretSpec) {
*out = *in *out = *in
out.TokenSecretReference = in.TokenSecretReference out.TokenSecretReference = in.TokenSecretReference
out.Authentication = in.Authentication
out.ManagedSecretReference = in.ManagedSecretReference out.ManagedSecretReference = in.ManagedSecretReference
} }
@@ -138,3 +156,35 @@ func (in *KubeSecretReference) DeepCopy() *KubeSecretReference {
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceAccountDetails) DeepCopyInto(out *ServiceAccountDetails) {
*out = *in
out.ServiceAccountSecretReference = in.ServiceAccountSecretReference
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountDetails.
func (in *ServiceAccountDetails) DeepCopy() *ServiceAccountDetails {
if in == nil {
return nil
}
out := new(ServiceAccountDetails)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceTokenDetails) DeepCopyInto(out *ServiceTokenDetails) {
*out = *in
out.ServiceTokenSecretReference = in.ServiceTokenSecretReference
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceTokenDetails.
func (in *ServiceTokenDetails) DeepCopy() *ServiceTokenDetails {
if in == nil {
return nil
}
out := new(ServiceTokenDetails)
in.DeepCopyInto(out)
return out
}

View File

@@ -35,6 +35,51 @@ spec:
spec: spec:
description: InfisicalSecretSpec defines the desired state of InfisicalSecret description: InfisicalSecretSpec defines the desired state of InfisicalSecret
properties: properties:
authentication:
properties:
serviceAccount:
properties:
environmentName:
type: string
projectId:
type: string
serviceAccountSecretReference:
properties:
secretName:
description: The name of the Kubernetes Secret
type: string
secretNamespace:
description: The name space where the Kubernetes Secret
is located
type: string
required:
- secretName
- secretNamespace
type: object
required:
- environmentName
- projectId
- serviceAccountSecretReference
type: object
serviceToken:
properties:
serviceTokenSecretReference:
properties:
secretName:
description: The name of the Kubernetes Secret
type: string
secretNamespace:
description: The name space where the Kubernetes Secret
is located
type: string
required:
- secretName
- secretNamespace
type: object
required:
- serviceTokenSecretReference
type: object
type: object
hostAPI: hostAPI:
description: Infisical host to pull secrets from description: Infisical host to pull secrets from
type: string type: string
@@ -62,6 +107,8 @@ spec:
- secretName - secretName
- secretNamespace - secretNamespace
type: object type: object
required:
- managedSecretReference
type: object type: object
status: status:
description: InfisicalSecretStatus defines the observed state of InfisicalSecret description: InfisicalSecretStatus defines the observed state of InfisicalSecret

View File

@@ -3,10 +3,22 @@ kind: InfisicalSecret
metadata: metadata:
name: infisicalsecret-sample name: infisicalsecret-sample
spec: spec:
hostAPI: https://app.infisical.com/api hostAPI: http://localhost:7070/api
tokenSecretReference: authentication:
secretName: service-token serviceAccount:
secretNamespace: default serviceAccountSecretReference:
secretName: service-account
secretNamespace: default
projectId: "6439ec224cfbf7ea2a95b651"
environmentName: "dev"
serviceToken:
serviceTokenSecretReference:
secretName: service-token
secretNamespace: default
managedSecretReference: managedSecretReference:
secretName: managed-secret secretName: managed-secret
secretNamespace: default secretNamespace: default
# To be depreciated soon
tokenSecretReference:
secretName: service-token
secretNamespace: default

View File

@@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: service-account
type: Opaque
stringData:
serviceAccountAccessKey: <>
serviceAccountPrivateKey: <>
serviceAccountPublicKey: <>

View File

@@ -7,6 +7,7 @@ import (
"github.com/Infisical/infisical/k8-operator/api/v1alpha1" "github.com/Infisical/infisical/k8-operator/api/v1alpha1"
"github.com/Infisical/infisical/k8-operator/packages/api" "github.com/Infisical/infisical/k8-operator/packages/api"
"github.com/Infisical/infisical/k8-operator/packages/model"
"github.com/Infisical/infisical/k8-operator/packages/util" "github.com/Infisical/infisical/k8-operator/packages/util"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
@@ -14,6 +15,10 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
) )
const SERVICE_ACCOUNT_ACCESS_KEY = "serviceAccountAccessKey"
const SERVICE_ACCOUNT_PUBLIC_KEY = "serviceAccountPublicKey"
const SERVICE_ACCOUNT_PRIVATE_KEY = "serviceAccountPrivateKey"
const INFISICAL_TOKEN_SECRET_KEY_NAME = "infisicalToken" const INFISICAL_TOKEN_SECRET_KEY_NAME = "infisicalToken"
const SECRET_VERSION_ANNOTATION = "secrets.infisical.com/version" // used to set the version of secrets via Etag const SECRET_VERSION_ANNOTATION = "secrets.infisical.com/version" // used to set the version of secrets via Etag
const OPERATOR_SETTINGS_CONFIGMAP_NAME = "infisical-config" const OPERATOR_SETTINGS_CONFIGMAP_NAME = "infisical-config"
@@ -64,24 +69,65 @@ func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Co
} }
func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) { func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) {
// default to new secret ref structure
secretName := infisicalSecret.Spec.Authentication.ServiceToken.ServiceTokenSecretReference.SecretName
secretNamespace := infisicalSecret.Spec.Authentication.ServiceToken.ServiceTokenSecretReference.SecretNamespace
// fall back to previous secret ref
if secretName == "" {
secretName = infisicalSecret.Spec.TokenSecretReference.SecretName
}
if secretNamespace == "" {
secretNamespace = infisicalSecret.Spec.TokenSecretReference.SecretNamespace
}
tokenSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{ tokenSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
Namespace: infisicalSecret.Spec.TokenSecretReference.SecretNamespace, Namespace: secretNamespace,
Name: infisicalSecret.Spec.TokenSecretReference.SecretName, Name: secretName,
}) })
if errors.IsNotFound(err) {
return "", nil
}
if err != nil { if err != nil {
return "", fmt.Errorf("failed to read Infisical token secret from secret named [%s] in namespace [%s]: with error [%w]", infisicalSecret.Spec.TokenSecretReference.SecretName, infisicalSecret.Spec.TokenSecretReference.SecretNamespace, err) return "", fmt.Errorf("failed to read Infisical token secret from secret named [%s] in namespace [%s]: with error [%w]", infisicalSecret.Spec.TokenSecretReference.SecretName, infisicalSecret.Spec.TokenSecretReference.SecretNamespace, err)
} }
infisicalServiceToken := tokenSecret.Data[INFISICAL_TOKEN_SECRET_KEY_NAME] infisicalServiceToken := tokenSecret.Data[INFISICAL_TOKEN_SECRET_KEY_NAME]
if infisicalServiceToken == nil {
return "", fmt.Errorf("the Infisical token is not set in the Kubernetes secret. Please add the key [%s] with the corresponding token value", INFISICAL_TOKEN_SECRET_KEY_NAME)
}
return strings.Replace(string(infisicalServiceToken), " ", "", -1), nil return strings.Replace(string(infisicalServiceToken), " ", "", -1), nil
} }
func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []util.SingleEnvironmentVariable, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) error { // Fetches service account credentials from a Kubernetes secret specified in the infisicalSecret object, extracts the access key, public key, and private key from the secret, and returns them as a ServiceAccountCredentials object.
// If any keys are missing or an error occurs, returns an empty object or an error object, respectively.
func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (serviceAccountDetails model.ServiceAccountDetails, err error) {
serviceAccountCredsFromKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
Namespace: infisicalSecret.Spec.Authentication.ServiceAccount.ServiceAccountSecretReference.SecretNamespace,
Name: infisicalSecret.Spec.Authentication.ServiceAccount.ServiceAccountSecretReference.SecretName,
})
if errors.IsNotFound(err) {
return model.ServiceAccountDetails{}, nil
}
if err != nil {
return model.ServiceAccountDetails{}, fmt.Errorf("something went wrong when fetching your service account credentials [err=%s]", err)
}
accessKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[SERVICE_ACCOUNT_ACCESS_KEY]
publicKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[SERVICE_ACCOUNT_PUBLIC_KEY]
privateKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[SERVICE_ACCOUNT_PRIVATE_KEY]
if accessKeyFromSecret == nil || publicKeyFromSecret == nil || privateKeyFromSecret == nil {
return model.ServiceAccountDetails{}, nil
}
return model.ServiceAccountDetails{AccessKey: string(accessKeyFromSecret), PrivateKey: string(privateKeyFromSecret), PublicKey: string(publicKeyFromSecret)}, nil
}
func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) error {
plainProcessedSecrets := make(map[string][]byte) plainProcessedSecrets := make(map[string][]byte)
for _, secret := range secretsFromAPI { for _, secret := range secretsFromAPI {
plainProcessedSecrets[secret.Key] = []byte(secret.Value) // plain process plainProcessedSecrets[secret.Key] = []byte(secret.Value) // plain process
@@ -109,7 +155,7 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context
return nil return nil
} }
func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, managedKubeSecret corev1.Secret, secretsFromAPI []util.SingleEnvironmentVariable, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) error { func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) error {
plainProcessedSecrets := make(map[string][]byte) plainProcessedSecrets := make(map[string][]byte)
for _, secret := range secretsFromAPI { for _, secret := range secretsFromAPI {
plainProcessedSecrets[secret.Key] = []byte(secret.Value) plainProcessedSecrets[secret.Key] = []byte(secret.Value)
@@ -131,6 +177,15 @@ func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) error { func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) error {
infisicalToken, err := r.GetInfisicalTokenFromKubeSecret(ctx, infisicalSecret) infisicalToken, err := r.GetInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
if err != nil {
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
}
serviceAccountCreds, err := r.GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
if err != nil {
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
}
r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, err) r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, err)
if err != nil { if err != nil {
return fmt.Errorf("unable to load Infisical Token from the specified Kubernetes secret with error [%w]", err) return fmt.Errorf("unable to load Infisical Token from the specified Kubernetes secret with error [%w]", err)
@@ -146,15 +201,33 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
return fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err) return fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err)
} }
// Get exiting Etag if exists
secretVersionBasedOnETag := "" secretVersionBasedOnETag := ""
if managedKubeSecret != nil { if managedKubeSecret != nil {
secretVersionBasedOnETag = managedKubeSecret.Annotations[SECRET_VERSION_ANNOTATION] secretVersionBasedOnETag = managedKubeSecret.Annotations[SECRET_VERSION_ANNOTATION]
} }
plainTextSecretsFromApi, fullEncryptedSecretsResponse, err := util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag) var plainTextSecretsFromApi []model.SingleEnvironmentVariable
if err != nil { var fullEncryptedSecretsResponse api.GetEncryptedSecretsV2Response
return fmt.Errorf("failed to get secrets because [err=%v]\n", err)
if serviceAccountCreds.AccessKey != "" || serviceAccountCreds.PrivateKey != "" || serviceAccountCreds.PublicKey != "" {
plainTextSecretsFromApi, fullEncryptedSecretsResponse, err = util.GetPlainTextSecretsViaServiceAccount(serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag)
if err != nil {
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service account")
} else if infisicalToken != "" {
plainTextSecretsFromApi, fullEncryptedSecretsResponse, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag)
if err != nil {
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service token")
} else {
return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets")
} }
if !fullEncryptedSecretsResponse.Modified { if !fullEncryptedSecretsResponse.Modified {
@@ -162,8 +235,6 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
return nil return nil
} }
fmt.Println("secret is modified so it needs to be created or updated")
if managedKubeSecret == nil { if managedKubeSecret == nil {
return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, fullEncryptedSecretsResponse) return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, fullEncryptedSecretsResponse)
} else { } else {

View File

@@ -57,12 +57,12 @@ require (
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.24.0 // indirect go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.1.0 golang.org/x/crypto v0.8.0
golang.org/x/net v0.7.0 // indirect golang.org/x/net v0.9.0 // indirect
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/sys v0.5.0 // indirect golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.5.0 // indirect golang.org/x/term v0.7.0 // indirect
golang.org/x/text v0.7.0 // indirect golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect

View File

@@ -392,6 +392,8 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -474,6 +476,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgk
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -566,11 +570,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -583,6 +591,8 @@ golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@@ -43,6 +43,49 @@ spec:
spec: spec:
description: InfisicalSecretSpec defines the desired state of InfisicalSecret description: InfisicalSecretSpec defines the desired state of InfisicalSecret
properties: properties:
authentication:
properties:
serviceAccount:
properties:
environmentName:
type: string
projectId:
type: string
serviceAccountSecretReference:
properties:
secretName:
description: The name of the Kubernetes Secret
type: string
secretNamespace:
description: The name space where the Kubernetes Secret is located
type: string
required:
- secretName
- secretNamespace
type: object
required:
- environmentName
- projectId
- serviceAccountSecretReference
type: object
serviceToken:
properties:
serviceTokenSecretReference:
properties:
secretName:
description: The name of the Kubernetes Secret
type: string
secretNamespace:
description: The name space where the Kubernetes Secret is located
type: string
required:
- secretName
- secretNamespace
type: object
required:
- serviceTokenSecretReference
type: object
type: object
hostAPI: hostAPI:
description: Infisical host to pull secrets from description: Infisical host to pull secrets from
type: string type: string
@@ -70,6 +113,8 @@ spec:
- secretName - secretName
- secretNamespace - secretNamespace
type: object type: object
required:
- managedSecretReference
type: object type: object
status: status:
description: InfisicalSecretStatus defines the observed state of InfisicalSecret description: InfisicalSecretStatus defines the observed state of InfisicalSecret

View File

@@ -78,3 +78,60 @@ func CallGetSecretsV2(httpClient *resty.Client, request GetEncryptedSecretsV2Req
return encryptedSecretsResponse, nil return encryptedSecretsResponse, nil
} }
func CallGetServiceTokenAccountDetailsV2(httpClient *resty.Client) (ServiceAccountDetailsResponse, error) {
var serviceAccountDetailsResponse ServiceAccountDetailsResponse
response, err := httpClient.
R().
SetResult(&serviceAccountDetailsResponse).
SetHeader("User-Agent", USER_AGENT_NAME).
Get(fmt.Sprintf("%v/v2/service-accounts/me", API_HOST_URL))
if err != nil {
return ServiceAccountDetailsResponse{}, fmt.Errorf("CallGetServiceTokenAccountDetailsV2: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return ServiceAccountDetailsResponse{}, fmt.Errorf("CallGetServiceTokenAccountDetailsV2: Unsuccessful response: [response=%s]", response)
}
return serviceAccountDetailsResponse, nil
}
func CallGetServiceAccountWorkspacePermissionsV2(httpClient *resty.Client) (ServiceAccountWorkspacePermissions, error) {
var serviceAccountWorkspacePermissionsResponse ServiceAccountWorkspacePermissions
response, err := httpClient.
R().
SetResult(&serviceAccountWorkspacePermissionsResponse).
SetHeader("User-Agent", USER_AGENT_NAME).
Get(fmt.Sprintf("%v/v2/service-accounts/<service-account-id>/permissions/workspace", API_HOST_URL))
if err != nil {
return ServiceAccountWorkspacePermissions{}, fmt.Errorf("CallGetServiceAccountWorkspacePermissionsV2: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return ServiceAccountWorkspacePermissions{}, fmt.Errorf("CallGetServiceAccountWorkspacePermissionsV2: Unsuccessful response: [response=%s]", response)
}
return serviceAccountWorkspacePermissionsResponse, nil
}
func CallGetServiceAccountKeysV2(httpClient *resty.Client, request GetServiceAccountKeysRequest) (GetServiceAccountKeysResponse, error) {
var serviceAccountKeysResponse GetServiceAccountKeysResponse
response, err := httpClient.
R().
SetResult(&serviceAccountKeysResponse).
SetHeader("User-Agent", USER_AGENT_NAME).
Get(fmt.Sprintf("%v/v2/service-accounts/%v/keys", API_HOST_URL, request.ServiceAccountId))
if err != nil {
return GetServiceAccountKeysResponse{}, fmt.Errorf("CallGetServiceAccountKeysV2: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return GetServiceAccountKeysResponse{}, fmt.Errorf("CallGetServiceAccountKeysV2: Unsuccessful response: [response=%s]", response)
}
return serviceAccountKeysResponse, nil
}

View File

@@ -65,3 +65,56 @@ type GetServiceTokenDetailsResponse struct {
Iv string `json:"iv"` Iv string `json:"iv"`
Tag string `json:"tag"` Tag string `json:"tag"`
} }
type ServiceAccountDetailsResponse struct {
ServiceAccount struct {
ID string `json:"_id"`
Name string `json:"name"`
Organization string `json:"organization"`
PublicKey string `json:"publicKey"`
LastUsed time.Time `json:"lastUsed"`
ExpiresAt time.Time `json:"expiresAt"`
} `json:"serviceAccount"`
}
type ServiceAccountWorkspacePermission struct {
ID string `json:"_id"`
ServiceAccount string `json:"serviceAccount"`
Workspace struct {
ID string `json:"_id"`
Name string `json:"name"`
AutoCapitalization bool `json:"autoCapitalization"`
Organization string `json:"organization"`
Environments []struct {
Name string `json:"name"`
Slug string `json:"slug"`
ID string `json:"_id"`
} `json:"environments"`
} `json:"workspace"`
Environment string `json:"environment"`
Read bool `json:"read"`
Write bool `json:"write"`
}
type ServiceAccountWorkspacePermissions struct {
ServiceAccountWorkspacePermission []ServiceAccountWorkspacePermissions `json:"serviceAccountWorkspacePermissions"`
}
type GetServiceAccountKeysRequest struct {
ServiceAccountId string `json:"id"`
}
type ServiceAccountKey struct {
ID string `json:"_id"`
EncryptedKey string `json:"encryptedKey"`
Nonce string `json:"nonce"`
Sender string `json:"sender"`
ServiceAccount string `json:"serviceAccount"`
Workspace string `json:"workspace"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type GetServiceAccountKeysResponse struct {
ServiceAccountKeys []ServiceAccountKey `json:"serviceAccountKeys"`
}

View File

@@ -3,6 +3,8 @@ package crypto
import ( import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"golang.org/x/crypto/nacl/box"
) )
func DecryptSymmetric(key []byte, encryptedPrivateKey []byte, tag []byte, IV []byte) ([]byte, error) { func DecryptSymmetric(key []byte, encryptedPrivateKey []byte, tag []byte, IV []byte) ([]byte, error) {
@@ -26,3 +28,8 @@ func DecryptSymmetric(key []byte, encryptedPrivateKey []byte, tag []byte, IV []b
return plaintext, nil return plaintext, nil
} }
func DecryptAsymmetric(ciphertext []byte, nonce []byte, publicKey []byte, privateKey []byte) (plainText []byte) {
plainTextToReturn, _ := box.Open(nil, ciphertext, (*[24]byte)(nonce), (*[32]byte)(publicKey), (*[32]byte)(privateKey))
return plainTextToReturn
}

View File

@@ -0,0 +1,14 @@
package model
type ServiceAccountDetails struct {
AccessKey string
PublicKey string
PrivateKey string
}
type SingleEnvironmentVariable struct {
Key string `json:"key"`
Value string `json:"value"`
Type string `json:"type"`
ID string `json:"_id"`
}

View File

@@ -7,16 +7,10 @@ import (
"github.com/Infisical/infisical/k8-operator/packages/api" "github.com/Infisical/infisical/k8-operator/packages/api"
"github.com/Infisical/infisical/k8-operator/packages/crypto" "github.com/Infisical/infisical/k8-operator/packages/crypto"
"github.com/Infisical/infisical/k8-operator/packages/model"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
) )
type SingleEnvironmentVariable struct {
Key string `json:"key"`
Value string `json:"value"`
Type string `json:"type"`
ID string `json:"_id"`
}
type DecodedSymmetricEncryptionDetails = struct { type DecodedSymmetricEncryptionDetails = struct {
Cipher []byte Cipher []byte
IV []byte IV []byte
@@ -54,7 +48,7 @@ func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsRe
return serviceTokenDetails, nil return serviceTokenDetails, nil
} }
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string) ([]SingleEnvironmentVariable, api.GetEncryptedSecretsV2Response, error) { func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string) ([]model.SingleEnvironmentVariable, api.GetEncryptedSecretsV2Response, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4) serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 4 { if len(serviceTokenParts) < 4 {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again") return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
@@ -100,6 +94,77 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string) ([
return plainTextSecrets, encryptedSecretsResponse, nil return plainTextSecrets, encryptedSecretsResponse, nil
} }
// Fetches plaintext secrets from an API endpoint using a service account.
// The function fetches the service account details and keys, decrypts the workspace key, fetches the encrypted secrets for the specified project and environment, and decrypts the secrets using the decrypted workspace key.
// Returns the plaintext secrets, encrypted secrets response, and any errors that occurred during the process.
func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string, etag string) ([]model.SingleEnvironmentVariable, api.GetEncryptedSecretsV2Response, error) {
httpClient := resty.New()
httpClient.SetAuthToken(serviceAccountCreds.AccessKey).
SetHeader("Accept", "application/json")
serviceAccountDetails, err := api.CallGetServiceTokenAccountDetailsV2(httpClient)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account details. [err=%v]", err)
}
serviceAccountKeys, err := api.CallGetServiceAccountKeysV2(httpClient, api.GetServiceAccountKeysRequest{ServiceAccountId: serviceAccountDetails.ServiceAccount.ID})
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account key details. [err=%v]", err)
}
// find key for requested project
var workspaceServiceAccountKey api.ServiceAccountKey
for _, serviceAccountKey := range serviceAccountKeys.ServiceAccountKeys {
if serviceAccountKey.Workspace == projectId {
workspaceServiceAccountKey = serviceAccountKey
}
}
if workspaceServiceAccountKey.ID == "" || workspaceServiceAccountKey.EncryptedKey == "" || workspaceServiceAccountKey.Nonce == "" || serviceAccountCreds.PublicKey == "" || serviceAccountCreds.PrivateKey == "" {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to find key for [projectId=%s] [err=%v]. Ensure that the given service account has access to given projectId", projectId, err)
}
cipherText, err := base64.StdEncoding.DecodeString(workspaceServiceAccountKey.EncryptedKey)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode EncryptedKey secrets because [err=%v]", err)
}
nonce, err := base64.StdEncoding.DecodeString(workspaceServiceAccountKey.Nonce)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode nonce secrets because [err=%v]", err)
}
publickey, err := base64.StdEncoding.DecodeString(serviceAccountCreds.PublicKey)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PublicKey secrets because [err=%v]", err)
}
privateKey, err := base64.StdEncoding.DecodeString(serviceAccountCreds.PrivateKey)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PrivateKey secrets because [err=%v]", err)
}
plainTextWorkspaceKey := crypto.DecryptAsymmetric(cipherText, nonce, publickey, privateKey)
encryptedSecretsResponse, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
WorkspaceId: projectId,
Environment: environmentName,
ETag: etag,
})
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to fetch secrets because [err=%v]", err)
}
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecretsResponse)
if err != nil {
return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get plain text secrets because [err=%v]", err)
}
return plainTextSecrets, encryptedSecretsResponse, nil
}
func GetBase64DecodedSymmetricEncryptionDetails(key string, cipher string, IV string, tag string) (DecodedSymmetricEncryptionDetails, error) { func GetBase64DecodedSymmetricEncryptionDetails(key string, cipher string, IV string, tag string) (DecodedSymmetricEncryptionDetails, error) {
cipherx, err := base64.StdEncoding.DecodeString(cipher) cipherx, err := base64.StdEncoding.DecodeString(cipher)
if err != nil { if err != nil {
@@ -129,8 +194,8 @@ func GetBase64DecodedSymmetricEncryptionDetails(key string, cipher string, IV st
}, nil }, nil
} }
func GetPlainTextSecrets(key []byte, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) ([]SingleEnvironmentVariable, error) { func GetPlainTextSecrets(key []byte, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) ([]model.SingleEnvironmentVariable, error) {
plainTextSecrets := []SingleEnvironmentVariable{} plainTextSecrets := []model.SingleEnvironmentVariable{}
for _, secret := range encryptedSecretsResponse.Secrets { for _, secret := range encryptedSecretsResponse.Secrets {
// Decrypt key // Decrypt key
key_iv, err := base64.StdEncoding.DecodeString(secret.SecretKeyIV) key_iv, err := base64.StdEncoding.DecodeString(secret.SecretKeyIV)
@@ -174,7 +239,7 @@ func GetPlainTextSecrets(key []byte, encryptedSecretsResponse api.GetEncryptedSe
return nil, fmt.Errorf("unable to symmetrically decrypt secret value") return nil, fmt.Errorf("unable to symmetrically decrypt secret value")
} }
plainTextSecret := SingleEnvironmentVariable{ plainTextSecret := model.SingleEnvironmentVariable{
Key: string(plainTextKey), Key: string(plainTextKey),
Value: string(plainTextValue), Value: string(plainTextValue),
Type: string(secret.Type), Type: string(secret.Type),