diff --git a/backend/spec.json b/backend/spec.json
index 31ef07373..0b0e275b6 100644
--- a/backend/spec.json
+++ b/backend/spec.json
@@ -3582,39 +3582,39 @@
},
"email": {
"type": "string",
- "example": ""
+ "example": "johndoe@gmail.com"
},
"firstName": {
"type": "string",
- "example": ""
+ "example": "John"
},
"lastName": {
"type": "string",
- "example": ""
+ "example": "Doe"
},
"publicKey": {
"type": "string",
- "example": ""
+ "example": "johns_nacl_public_key"
},
"encryptedPrivateKey": {
"type": "string",
- "example": ""
+ "example": "johns_enc_nacl_private_key"
},
"iv": {
"type": "string",
- "example": ""
+ "example": "iv_of_enc_nacl_private_key"
},
"tag": {
"type": "string",
- "example": ""
+ "example": "tag_of_enc_nacl_private_key"
},
"updatedAt": {
"type": "string",
- "example": ""
+ "example": "2023-01-13T14:16:12.210Z"
},
"createdAt": {
"type": "string",
- "example": ""
+ "example": "2023-01-13T14:16:12.210Z"
}
}
},
@@ -3630,27 +3630,27 @@
},
"email": {
"type": "string",
- "example": ""
+ "example": "johndoe@gmail.com"
},
"firstName": {
"type": "string",
- "example": ""
+ "example": "John"
},
"lastName": {
"type": "string",
- "example": ""
+ "example": "Doe"
},
"publicKey": {
"type": "string",
- "example": ""
+ "example": "johns_nacl_public_key"
},
"updatedAt": {
"type": "string",
- "example": ""
+ "example": "2023-01-13T14:16:12.210Z"
},
"createdAt": {
"type": "string",
- "example": ""
+ "example": "2023-01-13T14:16:12.210Z"
}
}
},
@@ -3676,27 +3676,27 @@
},
"email": {
"type": "string",
- "example": ""
+ "example": "johndoe@gmail.com"
},
"firstName": {
"type": "string",
- "example": ""
+ "example": "John"
},
"lastName": {
"type": "string",
- "example": ""
+ "example": "Doe"
},
"publicKey": {
"type": "string",
- "example": ""
+ "example": "johns_nacl_public_key"
},
"updatedAt": {
"type": "string",
- "example": ""
+ "example": "2023-01-13T14:16:12.210Z"
},
"createdAt": {
"type": "string",
- "example": ""
+ "example": "2023-01-13T14:16:12.210Z"
}
}
},
@@ -3723,7 +3723,7 @@
},
"name": {
"type": "string",
- "example": ""
+ "example": "Acme Corp."
},
"customerId": {
"type": "string",
@@ -3776,7 +3776,7 @@
"properties": {
"publicKey": {
"type": "string",
- "example": ""
+ "example": "senders_nacl_public_key"
}
}
},
@@ -3938,11 +3938,11 @@
},
"updatedAt": {
"type": "string",
- "example": ""
+ "example": "2023-01-13T14:16:12.210Z"
},
"createdAt": {
"type": "string",
- "example": ""
+ "example": "2023-01-13T14:16:12.210Z"
}
}
},
@@ -3962,15 +3962,15 @@
},
"email": {
"type": "string",
- "example": ""
+ "example": "johndoe@gmail.com"
},
"firstName": {
"type": "string",
- "example": ""
+ "example": "John"
},
"lastName": {
"type": "string",
- "example": ""
+ "example": "Doe"
}
}
},
@@ -4033,11 +4033,11 @@
},
"updatedAt": {
"type": "string",
- "example": ""
+ "example": "2023-01-13T14:16:12.210Z"
},
"createdAt": {
"type": "string",
- "example": ""
+ "example": "2023-01-13T14:16:12.210Z"
}
}
},
@@ -4087,7 +4087,7 @@
},
"type": {
"type": "string",
- "example": ""
+ "example": "shared"
},
"user": {
"type": "string",
@@ -4095,7 +4095,7 @@
},
"environment": {
"type": "string",
- "example": ""
+ "example": "dev"
},
"isDeleted": {
"type": "string",
diff --git a/backend/swagger/index.ts b/backend/swagger/index.ts
index d7a87357f..9f4d4732c 100644
--- a/backend/swagger/index.ts
+++ b/backend/swagger/index.ts
@@ -2,7 +2,6 @@
const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' });
const fs = require('fs').promises;
const yaml = require('js-yaml');
-const { secretSchema } = require('./schemas/index.ts');
/**
* Generates OpenAPI specs for all Infisical API endpoints:
@@ -43,25 +42,25 @@ const generateOpenAPISpec = async () => {
definitions: {
CurrentUser: {
_id: '',
- email: '',
- firstName: '',
- lastName: '',
- publicKey: '',
- encryptedPrivateKey: '',
- iv: '',
- tag: '',
- updatedAt: '',
- createdAt: ''
+ email: 'johndoe@gmail.com',
+ firstName: 'John',
+ lastName: 'Doe',
+ publicKey: 'johns_nacl_public_key',
+ encryptedPrivateKey: 'johns_enc_nacl_private_key',
+ iv: 'iv_of_enc_nacl_private_key',
+ tag: 'tag_of_enc_nacl_private_key',
+ updatedAt: '2023-01-13T14:16:12.210Z',
+ createdAt: '2023-01-13T14:16:12.210Z'
},
Membership: {
user: {
_id: '',
- email: '',
- firstName: '',
- lastName: '',
- publicKey: '',
- updatedAt: '',
- createdAt: ''
+ email: 'johndoe@gmail.com',
+ firstName: 'John',
+ lastName: 'Doe',
+ publicKey: 'johns_nacl_public_key',
+ updatedAt: '2023-01-13T14:16:12.210Z',
+ createdAt: '2023-01-13T14:16:12.210Z'
},
workspace: '',
role: 'admin'
@@ -69,12 +68,12 @@ const generateOpenAPISpec = async () => {
MembershipOrg: {
user: {
_id: '',
- email: '',
- firstName: '',
- lastName: '',
- publicKey: '',
- updatedAt: '',
- createdAt: ''
+ email: 'johndoe@gmail.com',
+ firstName: 'John',
+ lastName: 'Doe',
+ publicKey: 'johns_nacl_public_key',
+ updatedAt: '2023-01-13T14:16:12.210Z',
+ createdAt: '2023-01-13T14:16:12.210Z'
},
organization: '',
role: 'owner',
@@ -82,7 +81,7 @@ const generateOpenAPISpec = async () => {
},
Organization: {
_id: '',
- name: '',
+ name: 'Acme Corp.',
customerId: ''
},
Project: {
@@ -97,7 +96,7 @@ const generateOpenAPISpec = async () => {
encryptedkey: '',
nonce: '',
sender: {
- publicKey: ''
+ publicKey: 'senders_nacl_public_key'
},
receiver: '',
workspace: ''
@@ -141,16 +140,16 @@ const generateOpenAPISpec = async () => {
secretCommentCiphertext: '',
secretCommentIV: '',
secretCommentTag: '',
- updatedAt: '',
- createdAt: ''
+ updatedAt: '2023-01-13T14:16:12.210Z',
+ createdAt: '2023-01-13T14:16:12.210Z'
},
Log: {
_id: '',
user: {
_id: '',
- email: '',
- firstName: '',
- lastName: ''
+ email: 'johndoe@gmail.com',
+ firstName: 'John',
+ lastName: 'Doe'
},
workspace: '',
actionNames: [
@@ -171,8 +170,8 @@ const generateOpenAPISpec = async () => {
],
channel: 'cli',
ipAddress: '192.168.0.1',
- updatedAt: '',
- createdAt: ''
+ updatedAt: '2023-01-13T14:16:12.210Z',
+ createdAt: '2023-01-13T14:16:12.210Z'
},
SecretSnapshot: {
workspace: '',
@@ -188,9 +187,9 @@ const generateOpenAPISpec = async () => {
secret: '',
version: 1,
workspace: '',
- type: '',
+ type: 'shared',
user: '',
- environment: '',
+ environment: 'dev',
isDeleted: '',
secretKeyCiphertext: '',
secretKeyIV: '',
diff --git a/backend/swagger/schemas/index.ts b/backend/swagger/schemas/index.ts
deleted file mode 100644
index b0b427fce..000000000
--- a/backend/swagger/schemas/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-/* eslint-disable @typescript-eslint/no-var-requires */
-const secretSchema = require('./secretSchema.ts');
-
-module.exports = {
- secretSchema
-}
\ No newline at end of file
diff --git a/backend/swagger/schemas/secretSchema.ts b/backend/swagger/schemas/secretSchema.ts
deleted file mode 100644
index 85c6ce017..000000000
--- a/backend/swagger/schemas/secretSchema.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-const secretSchema = {
- _id: {
- type: 'string',
- format: 'objectId'
- },
- version: {
- type: 'number'
- }
-}
-
-module.exports = secretSchema;
\ No newline at end of file
diff --git a/docs/api-reference/endpoints/secrets/create.mdx b/docs/api-reference/endpoints/secrets/create.mdx
index 27c167f76..2fba642a7 100644
--- a/docs/api-reference/endpoints/secrets/create.mdx
+++ b/docs/api-reference/endpoints/secrets/create.mdx
@@ -2,3 +2,10 @@
title: "Create"
openapi: "POST /api/v2/secrets/"
---
+
+
+ Using this route requires understanding Infisical's system and cryptography.
+ It may be helpful to read through the
+ [introduction](/api-reference/overview/introduction) and [guide for creating
+ secrets](/api-reference/overview/examples/create-secrets).
+
diff --git a/docs/api-reference/endpoints/secrets/read.mdx b/docs/api-reference/endpoints/secrets/read.mdx
index c88fb0ec7..bc30ec8f2 100644
--- a/docs/api-reference/endpoints/secrets/read.mdx
+++ b/docs/api-reference/endpoints/secrets/read.mdx
@@ -2,3 +2,10 @@
title: "Retrieve"
openapi: "GET /api/v2/secrets/"
---
+
+
+ Using this route requires understanding Infisical's system and cryptography.
+ It may be helpful to read through the
+ [introduction](/api-reference/overview/introduction) and [guide for retrieving
+ secrets](/api-reference/overview/examples/retrieve-secrets).
+
diff --git a/docs/api-reference/endpoints/secrets/update.mdx b/docs/api-reference/endpoints/secrets/update.mdx
index 2193fccc3..c77a9de12 100644
--- a/docs/api-reference/endpoints/secrets/update.mdx
+++ b/docs/api-reference/endpoints/secrets/update.mdx
@@ -2,3 +2,10 @@
title: "Update"
openapi: "PATCH /api/v2/secrets/"
---
+
+
+ Using this route requires understanding Infisical's system and cryptography.
+ It may be helpful to read through the
+ [introduction](/api-reference/overview/introduction) and [guide for updating
+ secrets](/api-reference/overview/examples/update-secrets).
+
diff --git a/docs/api-reference/overview/authentication.mdx b/docs/api-reference/overview/authentication.mdx
index 8cc218253..9c155a96a 100644
--- a/docs/api-reference/overview/authentication.mdx
+++ b/docs/api-reference/overview/authentication.mdx
@@ -2,10 +2,14 @@
title: "Authentication"
---
-To authenticate requests with Infisical, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform. You can obtain an API key from your user settings.
+To authenticate requests with Infisical, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform. You can obtain an API key in User Settings > API Keys
+
+
+
+
It's important to keep your API key secure, as it grants access to your
- secrets in Infisical. For added security, consider rotating your API key on a
- regular basis.
+ secrets in Infisical. For added security, set a reasonable expiration time and
+ rotate your API key on a regular basis.
diff --git a/docs/api-reference/overview/examples/create-secrets.mdx b/docs/api-reference/overview/examples/create-secrets.mdx
index b2afe1467..319bd9d46 100644
--- a/docs/api-reference/overview/examples/create-secrets.mdx
+++ b/docs/api-reference/overview/examples/create-secrets.mdx
@@ -11,12 +11,12 @@ Prerequisites:
## Flow
-1. Get your (encrypted) private key.
+1. [Get your (encrypted) private key](/api-reference/endpoints/users/me).
2. Decrypt your (encrypted) private key with your password.
-3. Get the (encrypted) project key for the project.
+3. [Get the (encrypted) project key for the project.](/api-reference/endpoints/workspaces/workspace-key)
4. Decrypt the (encrypted) project key with your private key.
5. Encrypt your secret(s) with the project key.
-6. Send (encrypted) secret(s) to the Infical API
+6. [Send (encrypted) secret(s) to the Infical API](/api-reference/endpoints/secrets/create)
## Example
diff --git a/docs/api-reference/overview/examples/retrieve-secrets.mdx b/docs/api-reference/overview/examples/retrieve-secrets.mdx
index 2f34339c5..19d2d777b 100644
--- a/docs/api-reference/overview/examples/retrieve-secrets.mdx
+++ b/docs/api-reference/overview/examples/retrieve-secrets.mdx
@@ -11,11 +11,11 @@ Prerequisites:
## Flow
-1. Get your (encrypted) private key.
+1. [Get your (encrypted) private key.](/api-reference/endpoints/users/me)
2. Decrypt your (encrypted) private key with your password.
-3. Get the (encrypted) project key for the project.
+3. [Get the (encrypted) project key for the project.](/api-reference/endpoints/workspaces/workspace-key)
4. Decrypt the (encrypted) project key with your private key.
-5. Get secrets for a project and environment.
+5. [Get secrets for a project and environment.](/api-reference/endpoints/secrets/read)
6. Decrypt the (encrypted) secrets
## Example
diff --git a/docs/api-reference/overview/examples/update-secrets.mdx b/docs/api-reference/overview/examples/update-secrets.mdx
index b8566d9b3..99e3530ee 100644
--- a/docs/api-reference/overview/examples/update-secrets.mdx
+++ b/docs/api-reference/overview/examples/update-secrets.mdx
@@ -11,12 +11,12 @@ Prerequisites:
## Flow
-1. Get your (encrypted) private key.
+1. [Get your (encrypted) private key.](/api-reference/endpoints/users/me)
2. Decrypt your (encrypted) private key with your password.
-3. Get the project key for the project.
-4. Decrypt the project key with your private key.
+3. [Get the (encrypted) project key for the project.](/api-reference/endpoints/workspaces/workspace-key)
+4. Decrypt the (encrypted) project key with your private key.
5. Encrypt your secret(s) with the project key.
-6. Send (encrypted) updated secret(s) to the Infical API
+6. [Send (encrypted) updated secret(s) to the Infical API.](/api-reference/endpoints/secrets/update)
## Example
diff --git a/docs/api-reference/overview/introduction.mdx b/docs/api-reference/overview/introduction.mdx
index 7babef955..9865a644a 100644
--- a/docs/api-reference/overview/introduction.mdx
+++ b/docs/api-reference/overview/introduction.mdx
@@ -2,11 +2,6 @@
title: "Introduction"
---
-
- Infisical's REST API is currently unavailable and scheduled to go live on Jan
- 16!
-
-
Infisical's REST API provides users an alternative way to programmatically access and manage
secrets via HTTPS requests. This can be useful for automating tasks, such as
rotating credentials, or for integrating secret management into a larger system.
@@ -24,8 +19,9 @@ Using Infisical's API to manage secrets requires a basic understanding of the sy
- Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations.
- Infisical's system ensures greater security such that secrets are
- encrypted/decrypted on the client-side but requires users to properly
- implement cryptographic operations to maintain end-to-end encryption (E2EE).
- We're
+ Infisical's system requires that secrets be encrypted/decrypted on the
+ client-side to maintain E2EE. We strongly recommend you read up on the system
+ prior to using the Infisical API. The (opt-in) ability to retrieve secrets
+ back in decrypted format if you choose to share secrets with Infisical is on
+ our roadmap.
diff --git a/docs/images/api-key-add.png b/docs/images/api-key-add.png
new file mode 100644
index 000000000..a59e59cbe
Binary files /dev/null and b/docs/images/api-key-add.png differ
diff --git a/docs/images/api-key-dashboard.png b/docs/images/api-key-dashboard.png
new file mode 100644
index 000000000..29fb26519
Binary files /dev/null and b/docs/images/api-key-dashboard.png differ
diff --git a/docs/images/api-key-settings.png b/docs/images/api-key-settings.png
new file mode 100644
index 000000000..847a1e7b3
Binary files /dev/null and b/docs/images/api-key-settings.png differ
diff --git a/docs/spec.yaml b/docs/spec.yaml
index ced05d577..f715c5d7b 100644
--- a/docs/spec.yaml
+++ b/docs/spec.yaml
@@ -2208,31 +2208,31 @@ components:
example: ''
email:
type: string
- example: ''
+ example: johndoe@gmail.com
firstName:
type: string
- example: ''
+ example: John
lastName:
type: string
- example: ''
+ example: Doe
publicKey:
type: string
- example: ''
+ example: johns_nacl_public_key
encryptedPrivateKey:
type: string
- example: ''
+ example: johns_enc_nacl_private_key
iv:
type: string
- example: ''
+ example: iv_of_enc_nacl_private_key
tag:
type: string
- example: ''
+ example: tag_of_enc_nacl_private_key
updatedAt:
type: string
- example: ''
+ example: '2023-01-13T14:16:12.210Z'
createdAt:
type: string
- example: ''
+ example: '2023-01-13T14:16:12.210Z'
Membership:
type: object
properties:
@@ -2244,22 +2244,22 @@ components:
example: ''
email:
type: string
- example: ''
+ example: johndoe@gmail.com
firstName:
type: string
- example: ''
+ example: John
lastName:
type: string
- example: ''
+ example: Doe
publicKey:
type: string
- example: ''
+ example: johns_nacl_public_key
updatedAt:
type: string
- example: ''
+ example: '2023-01-13T14:16:12.210Z'
createdAt:
type: string
- example: ''
+ example: '2023-01-13T14:16:12.210Z'
workspace:
type: string
example: ''
@@ -2277,22 +2277,22 @@ components:
example: ''
email:
type: string
- example: ''
+ example: johndoe@gmail.com
firstName:
type: string
- example: ''
+ example: John
lastName:
type: string
- example: ''
+ example: Doe
publicKey:
type: string
- example: ''
+ example: johns_nacl_public_key
updatedAt:
type: string
- example: ''
+ example: '2023-01-13T14:16:12.210Z'
createdAt:
type: string
- example: ''
+ example: '2023-01-13T14:16:12.210Z'
organization:
type: string
example: ''
@@ -2310,7 +2310,7 @@ components:
example: ''
name:
type: string
- example: ''
+ example: Acme Corp.
customerId:
type: string
example: ''
@@ -2348,7 +2348,7 @@ components:
properties:
publicKey:
type: string
- example: ''
+ example: senders_nacl_public_key
receiver:
type: string
example: ''
@@ -2466,10 +2466,10 @@ components:
example: ''
updatedAt:
type: string
- example: ''
+ example: '2023-01-13T14:16:12.210Z'
createdAt:
type: string
- example: ''
+ example: '2023-01-13T14:16:12.210Z'
Log:
type: object
properties:
@@ -2484,13 +2484,13 @@ components:
example: ''
email:
type: string
- example: ''
+ example: johndoe@gmail.com
firstName:
type: string
- example: ''
+ example: John
lastName:
type: string
- example: ''
+ example: Doe
workspace:
type: string
example: ''
@@ -2533,10 +2533,10 @@ components:
example: 192.168.0.1
updatedAt:
type: string
- example: ''
+ example: '2023-01-13T14:16:12.210Z'
createdAt:
type: string
- example: ''
+ example: '2023-01-13T14:16:12.210Z'
SecretSnapshot:
type: object
properties:
@@ -2571,13 +2571,13 @@ components:
example: ''
type:
type: string
- example: ''
+ example: shared
user:
type: string
example: ''
environment:
type: string
- example: ''
+ example: dev
isDeleted:
type: string
example: ''
diff --git a/helm-charts/secrets-operator/Chart.yaml b/helm-charts/secrets-operator/Chart.yaml
index 26a402da3..9ab8e4898 100644
--- a/helm-charts/secrets-operator/Chart.yaml
+++ b/helm-charts/secrets-operator/Chart.yaml
@@ -13,7 +13,7 @@ type: application
# 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.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 0.1.0
+version: 0.1.1
# 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
# follow Semantic Versioning. They should reflect the version the application is using.
diff --git a/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml b/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml
index c5b82fa14..e3e3517d5 100644
--- a/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml
+++ b/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml
@@ -35,9 +35,6 @@ spec:
spec:
description: InfisicalSecretSpec defines the desired state of InfisicalSecret
properties:
- environment:
- description: The Infisical environment such as dev, prod, testing
- type: string
hostAPI:
default: https://app.infisical.com/api
description: Infisical host to pull secrets from
@@ -54,9 +51,6 @@ spec:
- secretName
- secretNamespace
type: object
- projectId:
- description: The Infisical project id
- type: string
tokenSecretReference:
properties:
secretName:
@@ -69,9 +63,6 @@ spec:
- secretName
- secretNamespace
type: object
- required:
- - environment
- - projectId
type: object
status:
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go
index 7940474f9..8f8fbbed9 100644
--- a/k8-operator/api/v1alpha1/infisicalsecret_types.go
+++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go
@@ -16,17 +16,11 @@ type KubeSecretReference struct {
// InfisicalSecretSpec defines the desired state of InfisicalSecret
type InfisicalSecretSpec struct {
- TokenSecretReference KubeSecretReference `json:"tokenSecretReference,omitempty"`
+ // +kubebuilder:validation:Required
+ TokenSecretReference KubeSecretReference `json:"tokenSecretReference,omitempty"`
+ // +kubebuilder:validation:Required
ManagedSecretReference KubeSecretReference `json:"managedSecretReference,omitempty"`
- // The Infisical project id
- // +kubebuilder:validation:Required
- ProjectId string `json:"projectId"`
-
- // The Infisical environment such as dev, prod, testing
- // +kubebuilder:validation:Required
- Environment string `json:"environment"`
-
// Infisical host to pull secrets from
// +kubebuilder:default="https://app.infisical.com/api"
HostAPI string `json:"hostAPI,omitempty"`
diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml
index c885fbdde..2f99ef443 100644
--- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml
+++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml
@@ -35,9 +35,6 @@ spec:
spec:
description: InfisicalSecretSpec defines the desired state of InfisicalSecret
properties:
- environment:
- description: The Infisical environment such as dev, prod, testing
- type: string
hostAPI:
default: https://app.infisical.com/api
description: Infisical host to pull secrets from
@@ -54,9 +51,6 @@ spec:
- secretName
- secretNamespace
type: object
- projectId:
- description: The Infisical project id
- type: string
tokenSecretReference:
properties:
secretName:
@@ -69,9 +63,6 @@ spec:
- secretName
- secretNamespace
type: object
- required:
- - environment
- - projectId
type: object
status:
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
diff --git a/k8-operator/config/samples/deployment.yaml b/k8-operator/config/samples/deployment.yaml
new file mode 100644
index 000000000..c43a9e544
--- /dev/null
+++ b/k8-operator/config/samples/deployment.yaml
@@ -0,0 +1,26 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-deployment-2
+ labels:
+ app: nginx
+ annotations:
+ secrets.infisical.com/auto-reload: "true"
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.14.2
+ envFrom:
+ - secretRef:
+ name: managed-secret
+ ports:
+ - containerPort: 80
\ No newline at end of file
diff --git a/k8-operator/config/samples/sample.yaml b/k8-operator/config/samples/sample.yaml
index ad352d3e7..236e55baf 100644
--- a/k8-operator/config/samples/sample.yaml
+++ b/k8-operator/config/samples/sample.yaml
@@ -3,11 +3,10 @@ kind: InfisicalSecret
metadata:
name: infisicalsecret-sample
spec:
- projectId: 62faf98ae0b05e8529b5da46
- environment: dev
+ hostAPI: https://app.infisical.com/api
tokenSecretReference:
secretName: service-token
- secretNamespace: first-project
+ secretNamespace: default
managedSecretReference:
secretName: managed-secret
- secretNamespace: first-project
+ secretNamespace: default
diff --git a/k8-operator/config/samples/serviceTokenSecret.yaml b/k8-operator/config/samples/serviceTokenSecret.yaml
new file mode 100644
index 000000000..3fd2e1b8a
--- /dev/null
+++ b/k8-operator/config/samples/serviceTokenSecret.yaml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: service-token
+type: Opaque
+data:
+ infisicalToken:
\ No newline at end of file
diff --git a/k8-operator/controllers/auto_redeployment.go b/k8-operator/controllers/auto_redeployment.go
new file mode 100644
index 000000000..468b6e75d
--- /dev/null
+++ b/k8-operator/controllers/auto_redeployment.go
@@ -0,0 +1,121 @@
+package controllers
+
+import (
+ "context"
+ "fmt"
+ "sync"
+
+ "github.com/Infisical/infisical/k8-operator/api/v1alpha1"
+ v1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/types"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+const DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX = "secrets.infisical.com/managed-secret"
+const AUTO_RELOAD_DEPLOYMENT_ANNOTATION = "secrets.infisical.com/auto-reload" // needs to be set to true for a deployment to start auto redeploying
+
+func (r *InfisicalSecretReconciler) ReconcileDeploymentsWithManagedSecrets(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (int, error) {
+ listOfDeployments := &v1.DeploymentList{}
+ err := r.Client.List(ctx, listOfDeployments, &client.ListOptions{Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace})
+ if err != nil {
+ return 0, fmt.Errorf("unable to get deployments in the [namespace=%v] [err=%v]", infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, err)
+ }
+
+ managedKubeSecretNameAndNamespace := types.NamespacedName{
+ Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
+ Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
+ }
+
+ managedKubeSecret := &corev1.Secret{}
+ err = r.Client.Get(ctx, managedKubeSecretNameAndNamespace, managedKubeSecret)
+ if err != nil {
+ return 0, fmt.Errorf("unable to fetch Kubernetes secret to update deployment: %v", err)
+ }
+
+ // Create a channel to receive errors from goroutines
+ errChan := make(chan error, len(listOfDeployments.Items))
+
+ wg := sync.WaitGroup{}
+ wg.Add(len(listOfDeployments.Items))
+ go func() {
+ wg.Wait()
+ close(errChan)
+ }()
+
+ // Iterate over the deployments and check if they use the managed secret
+ for _, deployment := range listOfDeployments.Items {
+ if deployment.Annotations[AUTO_RELOAD_DEPLOYMENT_ANNOTATION] == "true" && r.IsDeploymentUsingManagedSecret(deployment, infisicalSecret) {
+ // Start a goroutine to reconcile the deployment
+ go func(d v1.Deployment, s corev1.Secret) {
+ defer wg.Done()
+ if err := r.ReconcileDeployment(ctx, d, s); err != nil {
+ errChan <- err
+ }
+ }(deployment, *managedKubeSecret)
+ }
+ }
+
+ // Collect any errors that were sent through the channel
+ var errs []error
+ for err := range errChan {
+ errs = append(errs, err)
+ }
+
+ if len(errs) > 0 {
+ return 0, fmt.Errorf("unable to reconcile some deployments: %v", errs)
+ }
+
+ return len(listOfDeployments.Items), nil
+}
+
+// Check if the deployment uses managed secrets
+func (r *InfisicalSecretReconciler) IsDeploymentUsingManagedSecret(deployment v1.Deployment, infisicalSecret v1alpha1.InfisicalSecret) bool {
+ managedSecretName := infisicalSecret.Spec.ManagedSecretReference.SecretName
+ for _, container := range deployment.Spec.Template.Spec.Containers {
+ for _, envFrom := range container.EnvFrom {
+ if envFrom.SecretRef != nil && envFrom.SecretRef.LocalObjectReference.Name == managedSecretName {
+ return true
+ }
+ }
+ for _, env := range container.Env {
+ if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil && env.ValueFrom.SecretKeyRef.LocalObjectReference.Name == managedSecretName {
+ return true
+ }
+ }
+ }
+ for _, volume := range deployment.Spec.Template.Spec.Volumes {
+ if volume.Secret != nil && volume.Secret.SecretName == managedSecretName {
+ return true
+ }
+ }
+
+ return false
+}
+
+// This function ensures that a deployment is in sync with a Kubernetes secret by comparing their versions.
+// If the version of the secret is different from the version annotation on the deployment, the annotation is updated to trigger a restart of the deployment.
+func (r *InfisicalSecretReconciler) ReconcileDeployment(ctx context.Context, deployment v1.Deployment, secret corev1.Secret) error {
+ annotationKey := fmt.Sprintf("%s.%s", DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX, secret.Name)
+ annotationValue := secret.Annotations[SECRET_VERSION_ANNOTATION]
+
+ if deployment.Annotations[annotationKey] == annotationValue &&
+ deployment.Spec.Template.Annotations[annotationKey] == annotationValue {
+ fmt.Printf("The [deploymentName=%v] is already using the most up to date managed secrets. No action required.\n", deployment.ObjectMeta.Name)
+ return nil
+ }
+
+ fmt.Printf("deployment is using outdated managed secret. Starting re-deployment [deploymentName=%v]\n", deployment.ObjectMeta.Name)
+
+ if deployment.Spec.Template.Annotations == nil {
+ deployment.Spec.Template.Annotations = make(map[string]string)
+ }
+
+ deployment.Annotations[annotationKey] = annotationValue
+ deployment.Spec.Template.Annotations[annotationKey] = annotationValue
+
+ if err := r.Client.Update(ctx, &deployment); err != nil {
+ return fmt.Errorf("failed to update deployment annotation: %v", err)
+ }
+ return nil
+}
diff --git a/k8-operator/controllers/conditions.go b/k8-operator/controllers/conditions.go
new file mode 100644
index 000000000..dea822088
--- /dev/null
+++ b/k8-operator/controllers/conditions.go
@@ -0,0 +1,95 @@
+package controllers
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/Infisical/infisical/k8-operator/api/v1alpha1"
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) error {
+ if infisicalSecret.Status.Conditions == nil {
+ infisicalSecret.Status.Conditions = []metav1.Condition{}
+ }
+
+ if errorToConditionOn != nil {
+ meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
+ Type: "secrets.infisical.com/ReadyToSyncSecrets",
+ Status: metav1.ConditionFalse,
+ Reason: "Error",
+ Message: "Failed to sync secrets. This can be caused by invalid service token or an invalid API host that is set. Check operator logs for more info",
+ })
+
+ meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
+ Type: "secrets.infisical.com/AutoRedeployReady",
+ Status: metav1.ConditionFalse,
+ Reason: "Stopped",
+ Message: "Auto redeployment has been stopped because the operator failed to sync secrets",
+ })
+ } else {
+ meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
+ Type: "secrets.infisical.com/ReadyToSyncSecrets",
+ Status: metav1.ConditionTrue,
+ Reason: "OK",
+ Message: "Infisical controller has started syncing your secrets",
+ })
+ }
+
+ return r.Client.Status().Update(ctx, infisicalSecret)
+}
+
+func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) {
+ if infisicalSecret.Status.Conditions == nil {
+ infisicalSecret.Status.Conditions = []metav1.Condition{}
+ }
+
+ if errorToConditionOn == nil {
+ meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
+ Type: "secrets.infisical.com/LoadedInfisicalToken",
+ Status: metav1.ConditionTrue,
+ Reason: "OK",
+ Message: "Infisical controller has located the Infisical token in provided Kubernetes secret",
+ })
+ } else {
+ meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
+ Type: "secrets.infisical.com/LoadedInfisicalToken",
+ Status: metav1.ConditionFalse,
+ Reason: "Error",
+ Message: fmt.Sprintf("Failed to load Infisical Token from the provided Kubernetes secret because: %v", errorToConditionOn),
+ })
+ }
+
+ err := r.Client.Status().Update(ctx, infisicalSecret)
+ if err != nil {
+ fmt.Println("Could not set condition for LoadedInfisicalToken")
+ }
+}
+
+func (r *InfisicalSecretReconciler) SetInfisicalAutoRedeploymentReady(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, numDeployments int, errorToConditionOn error) {
+ if infisicalSecret.Status.Conditions == nil {
+ infisicalSecret.Status.Conditions = []metav1.Condition{}
+ }
+
+ if errorToConditionOn == nil {
+ meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
+ Type: "secrets.infisical.com/AutoRedeployReady",
+ Status: metav1.ConditionTrue,
+ Reason: "OK",
+ Message: fmt.Sprintf("Infisical has found %v deployments which are ready to be auto redeployed when secrets change", numDeployments),
+ })
+ } else {
+ meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
+ Type: "secrets.infisical.com/AutoRedeployReady",
+ Status: metav1.ConditionFalse,
+ Reason: "Error",
+ Message: fmt.Sprintf("Failed reconcile deployments because: %v", errorToConditionOn),
+ })
+ }
+
+ err := r.Client.Status().Update(ctx, infisicalSecret)
+ if err != nil {
+ fmt.Println("Could not set condition for AutoRedeployReady")
+ }
+}
diff --git a/k8-operator/controllers/infisicalsecret_controller.go b/k8-operator/controllers/infisicalsecret_controller.go
index f6422e177..f32cd0a2e 100644
--- a/k8-operator/controllers/infisicalsecret_controller.go
+++ b/k8-operator/controllers/infisicalsecret_controller.go
@@ -2,16 +2,17 @@ package controllers
import (
"context"
+ "fmt"
"time"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/log"
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1"
+ "github.com/Infisical/infisical/k8-operator/packages/api"
)
// InfisicalSecretReconciler reconciles a InfisicalSecret object
@@ -31,19 +32,18 @@ type InfisicalSecretReconciler struct {
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile
func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
- log := log.FromContext(ctx)
-
+ requeueTime := time.Minute * 1
var infisicalSecretCR v1alpha1.InfisicalSecret
+
err := r.Get(ctx, req.NamespacedName, &infisicalSecretCR)
-
- requeueTime := time.Minute * 5
-
if err != nil {
if errors.IsNotFound(err) {
- log.Info("Infisical Secret not found")
- return ctrl.Result{}, nil
+ fmt.Printf("Infisical Secret CRD not found [err=%v]", err)
+ return ctrl.Result{
+ Requeue: false,
+ }, nil
} else {
- log.Error(err, "Unable to fetch Infisical Secret from cluster. Will retry")
+ fmt.Printf("Unable to fetch Infisical Secret CRD from cluster because [err=%v]", err)
return ctrl.Result{
RequeueAfter: requeueTime,
}, nil
@@ -52,13 +52,28 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
// Check if the resource is already marked for deletion
if infisicalSecretCR.GetDeletionTimestamp() != nil {
- return ctrl.Result{}, nil
+ return ctrl.Result{
+ Requeue: false,
+ }, nil
}
+ // set the api url based on the CRD
+ api.API_HOST_URL = infisicalSecretCR.Spec.HostAPI
+
err = r.ReconcileInfisicalSecret(ctx, infisicalSecretCR)
r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCR, err)
+
if err != nil {
- log.Error(err, "Unable to reconcile Infisical Secret and will try again")
+ fmt.Printf("unable to reconcile Infisical Secret because [err=%v]. Will requeue after [requeueTime=%v]\n", err, requeueTime)
+ return ctrl.Result{
+ RequeueAfter: requeueTime,
+ }, nil
+ }
+
+ numDeployments, err := r.ReconcileDeploymentsWithManagedSecrets(ctx, infisicalSecretCR)
+ r.SetInfisicalAutoRedeploymentReady(ctx, &infisicalSecretCR, numDeployments, err)
+ if err != nil {
+ fmt.Printf("unable to reconcile auto redeployment because [err=%v]", err)
return ctrl.Result{
RequeueAfter: requeueTime,
}, nil
@@ -73,6 +88,6 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
// SetupWithManager sets up the controller with the Manager.
func (r *InfisicalSecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
- For(&secretsv1alpha1.InfisicalSecret{}). // TODO we should also be watching secrets with the name specifed
+ For(&secretsv1alpha1.InfisicalSecret{}).
Complete(r)
}
diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go
index be4e35dd5..8428cbec1 100644
--- a/k8-operator/controllers/infisicalsecret_helper.go
+++ b/k8-operator/controllers/infisicalsecret_helper.go
@@ -6,16 +6,16 @@ import (
"strings"
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
- api "github.com/Infisical/infisical/k8-operator/packages/api"
- models "github.com/Infisical/infisical/k8-operator/packages/models"
+ "github.com/Infisical/infisical/k8-operator/packages/api"
+ "github.com/Infisical/infisical/k8-operator/packages/util"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
- "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
const INFISICAL_TOKEN_SECRET_KEY_NAME = "infisicalToken"
+const SECRET_VERSION_ANNOTATION = "secrets.infisical.com/version" // used to set the version of secrets via Etag
func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Context, namespacedName types.NamespacedName) (*corev1.Secret, error) {
kubeSecret := &corev1.Secret{}
@@ -27,14 +27,14 @@ func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Co
return kubeSecret, err
}
-func (r *InfisicalSecretReconciler) GetInfisicalToken(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) {
+func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) {
tokenSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
Namespace: infisicalSecret.Spec.TokenSecretReference.SecretNamespace,
Name: infisicalSecret.Spec.TokenSecretReference.SecretName,
})
if err != nil {
- return "", fmt.Errorf("failed to read Infisical token secret from secret named [%s] in namespace [%s]: with error [%w]", infisicalSecret.Spec.ManagedSecretReference.SecretName, infisicalSecret.Spec.ManagedSecretReference.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]
@@ -45,7 +45,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalToken(ctx context.Context, infis
return strings.Replace(string(infisicalServiceToken), " ", "", -1), nil
}
-func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []models.SingleEnvironmentVariable) error {
+func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []util.SingleEnvironmentVariable, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) error {
plainProcessedSecrets := make(map[string][]byte)
for _, secret := range secretsFromAPI {
plainProcessedSecrets[secret.Key] = []byte(secret.Value) // plain process
@@ -56,6 +56,9 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context
ObjectMeta: metav1.ObjectMeta{
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
+ Annotations: map[string]string{
+ SECRET_VERSION_ANNOTATION: encryptedSecretsResponse.ETag,
+ },
},
Type: "Opaque",
Data: plainProcessedSecrets,
@@ -70,13 +73,17 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context
return nil
}
-func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, managedKubeSecret corev1.Secret, secretsFromAPI []models.SingleEnvironmentVariable) error {
+func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, managedKubeSecret corev1.Secret, secretsFromAPI []util.SingleEnvironmentVariable, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) error {
plainProcessedSecrets := make(map[string][]byte)
for _, secret := range secretsFromAPI {
plainProcessedSecrets[secret.Key] = []byte(secret.Value)
}
managedKubeSecret.Data = plainProcessedSecrets
+ managedKubeSecret.ObjectMeta.Annotations = map[string]string{
+ SECRET_VERSION_ANNOTATION: encryptedSecretsResponse.ETag,
+ }
+
err := r.Client.Update(ctx, &managedKubeSecret)
if err != nil {
return fmt.Errorf("unable to update Kubernetes secret because [%w]", err)
@@ -87,12 +94,13 @@ func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context
}
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) error {
- infisicalToken, err := r.GetInfisicalToken(ctx, infisicalSecret)
+ infisicalToken, err := r.GetInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, err)
if err != nil {
return fmt.Errorf("unable to load Infisical Token from the specified Kubernetes secret with error [%w]", err)
}
+ // Look for managed secret by name and namespace
managedKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
@@ -102,72 +110,28 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
return fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err)
}
- secretsFromApi, err := api.GetAllEnvironmentVariables(infisicalSecret.Spec.ProjectId, infisicalSecret.Spec.Environment, infisicalToken, infisicalSecret.Spec.HostAPI)
+ secretVersionBasedOnETag := ""
- if err != nil {
- return err
+ if managedKubeSecret != nil {
+ secretVersionBasedOnETag = managedKubeSecret.Annotations[SECRET_VERSION_ANNOTATION]
}
+ plainTextSecretsFromApi, fullEncryptedSecretsResponse, err := util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag)
+ if err != nil {
+ return fmt.Errorf("failed to get secrets because [err=%v]\n", err)
+ }
+
+ if !fullEncryptedSecretsResponse.Modified {
+ fmt.Println("No secrets modified so reconcile not needed", "Etag:", fullEncryptedSecretsResponse.ETag, "Modified:", fullEncryptedSecretsResponse.Modified)
+ return nil
+ }
+
+ fmt.Println("secret is modified so it needs to be created or updated")
+
if managedKubeSecret == nil {
- return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, secretsFromApi)
+ return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, fullEncryptedSecretsResponse)
} else {
- return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, secretsFromApi)
+ return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, plainTextSecretsFromApi, fullEncryptedSecretsResponse)
}
}
-
-// Conditions
-
-func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) {
- if infisicalSecret.Status.Conditions == nil {
- infisicalSecret.Status.Conditions = []metav1.Condition{}
- }
-
- if errorToConditionOn == nil {
- meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
- Type: "secrets.infisical.com/ReadyToSyncSecrets",
- Status: metav1.ConditionTrue,
- Reason: "OK",
- Message: "Infisical controller has started syncing your secrets",
- })
- } else {
- meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
- Type: "secrets.infisical.com/ReadyToSyncSecrets",
- Status: metav1.ConditionFalse,
- Reason: "Error",
- Message: fmt.Sprintf("Failed to update secret because: %v", errorToConditionOn),
- })
- }
-
- err := r.Client.Status().Update(ctx, infisicalSecret)
- if err != nil {
- fmt.Println("Could not set condition", err)
- }
-}
-
-func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) {
- if infisicalSecret.Status.Conditions == nil {
- infisicalSecret.Status.Conditions = []metav1.Condition{}
- }
-
- if errorToConditionOn == nil {
- meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
- Type: "secrets.infisical.com/LoadedInfisicalToken",
- Status: metav1.ConditionTrue,
- Reason: "OK",
- Message: "Infisical controller has located the Infisical token in provided Kubernetes secret",
- })
- } else {
- meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{
- Type: "secrets.infisical.com/LoadedInfisicalToken",
- Status: metav1.ConditionFalse,
- Reason: "Error",
- Message: fmt.Sprintf("Failed to load Infisical Token because: %v", errorToConditionOn),
- })
- }
-
- err := r.Client.Status().Update(ctx, infisicalSecret)
- if err != nil {
- fmt.Println("Could not set condition for LoadedInfisicalToken")
- }
-}
diff --git a/k8-operator/kubectl-install/install-secrets-operator.yaml b/k8-operator/kubectl-install/install-secrets-operator.yaml
index 6a3cb8e6d..7862fe7c3 100644
--- a/k8-operator/kubectl-install/install-secrets-operator.yaml
+++ b/k8-operator/kubectl-install/install-secrets-operator.yaml
@@ -43,9 +43,6 @@ spec:
spec:
description: InfisicalSecretSpec defines the desired state of InfisicalSecret
properties:
- environment:
- description: The Infisical environment such as dev, prod, testing
- type: string
hostAPI:
default: https://app.infisical.com/api
description: Infisical host to pull secrets from
@@ -62,9 +59,6 @@ spec:
- secretName
- secretNamespace
type: object
- projectId:
- description: The Infisical project id
- type: string
tokenSecretReference:
properties:
secretName:
@@ -77,9 +71,6 @@ spec:
- secretName
- secretNamespace
type: object
- required:
- - environment
- - projectId
type: object
status:
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
diff --git a/k8-operator/packages/api/api.go b/k8-operator/packages/api/api.go
index 64f8add46..46969e320 100644
--- a/k8-operator/packages/api/api.go
+++ b/k8-operator/packages/api/api.go
@@ -1,177 +1,80 @@
package api
import (
- "encoding/base64"
- "errors"
"fmt"
- "regexp"
- "strings"
- "github.com/Infisical/infisical/k8-operator/packages/crypto"
- "github.com/Infisical/infisical/k8-operator/packages/models"
"github.com/go-resty/resty/v2"
- "golang.org/x/crypto/nacl/box"
)
-func GetAllEnvironmentVariables(projectId string, envName string, infisicalToken string, hostAPI string) ([]models.SingleEnvironmentVariable, error) {
- envsFromApi, err := GetSecretsFromAPIUsingInfisicalToken(infisicalToken, envName, projectId, hostAPI)
- if err != nil {
- return nil, err
- }
+const USER_AGENT_NAME = "k8-operator"
- return SubstituteSecrets(envsFromApi), nil
-}
-
-func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string, projectId string, hostAPI string) ([]models.SingleEnvironmentVariable, error) {
- if infisicalToken == "" || projectId == "" || envName == "" {
- return nil, errors.New("infisical token, project id and or environment name cannot be empty")
- }
-
- splitToken := strings.Split(infisicalToken, ",")
- JTWToken := splitToken[0]
- temPrivateKey := splitToken[1]
-
- // create http client
- httpClient := resty.New().
- SetAuthToken(JTWToken).
- SetHeader("Accept", "application/json")
-
- var pullSecretsByInfisicalTokenResponse models.PullSecretsByInfisicalTokenResponse
+func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request GetEncryptedWorkspaceKeyRequest) (GetEncryptedWorkspaceKeyResponse, error) {
+ endpoint := fmt.Sprintf("%v/v2/workspace/%v/encrypted-key", API_HOST_URL, request.WorkspaceId)
+ var result GetEncryptedWorkspaceKeyResponse
response, err := httpClient.
R().
- SetQueryParam("environment", envName).
- SetQueryParam("channel", "cli").
- SetResult(&pullSecretsByInfisicalTokenResponse).
- Get(fmt.Sprintf("%v/v1/secret/%v/service-token", hostAPI, projectId))
+ SetResult(&result).
+ SetHeader("User-Agent", USER_AGENT_NAME).
+ Get(endpoint)
if err != nil {
- return nil, err
+ return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unable to complete api request [err=%s]", err)
}
if response.StatusCode() > 299 {
- return nil, fmt.Errorf(response.Status())
+ return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unsuccessful response: [response=%s]", response)
}
- // Get workspace key
- workspaceKey, err := base64.StdEncoding.DecodeString(pullSecretsByInfisicalTokenResponse.Key.EncryptedKey)
- if err != nil {
- return nil, err
- }
-
- nonce, err := base64.StdEncoding.DecodeString(pullSecretsByInfisicalTokenResponse.Key.Nonce)
- if err != nil {
- return nil, err
- }
-
- senderPublicKey, err := base64.StdEncoding.DecodeString(pullSecretsByInfisicalTokenResponse.Key.Sender.PublicKey)
- if err != nil {
- return nil, err
- }
-
- currentUsersPrivateKey, err := base64.StdEncoding.DecodeString(temPrivateKey)
- if err != nil {
- return nil, err
- }
-
- workspaceKeyInBytes, _ := box.Open(nil, workspaceKey, (*[24]byte)(nonce), (*[32]byte)(senderPublicKey), (*[32]byte)(currentUsersPrivateKey))
- var listOfEnv []models.SingleEnvironmentVariable
-
- for _, secret := range pullSecretsByInfisicalTokenResponse.Secrets {
- key_iv, _ := base64.StdEncoding.DecodeString(secret.SecretKey.Iv)
- key_tag, _ := base64.StdEncoding.DecodeString(secret.SecretKey.Tag)
- key_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretKey.Ciphertext)
-
- plainTextKey, err := crypto.DecryptSymmetric(workspaceKeyInBytes, key_ciphertext, key_tag, key_iv)
- if err != nil {
- return nil, err
- }
-
- value_iv, _ := base64.StdEncoding.DecodeString(secret.SecretValue.Iv)
- value_tag, _ := base64.StdEncoding.DecodeString(secret.SecretValue.Tag)
- value_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretValue.Ciphertext)
-
- plainTextValue, err := crypto.DecryptSymmetric(workspaceKeyInBytes, value_ciphertext, value_tag, value_iv)
- if err != nil {
- return nil, err
- }
-
- env := models.SingleEnvironmentVariable{
- Key: string(plainTextKey),
- Value: string(plainTextValue),
- }
-
- listOfEnv = append(listOfEnv, env)
- }
-
- return listOfEnv, nil
+ return result, 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
+func CallGetServiceTokenDetailsV2(httpClient *resty.Client) (GetServiceTokenDetailsResponse, error) {
+ var tokenDetailsResponse GetServiceTokenDetailsResponse
+ response, err := httpClient.
+ R().
+ SetResult(&tokenDetailsResponse).
+ SetHeader("User-Agent", USER_AGENT_NAME).
+ Get(fmt.Sprintf("%v/v2/service-token", API_HOST_URL))
+
+ if err != nil {
+ return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unable to complete api request [err=%s]", err)
}
- 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
- }
+ if response.IsError() {
+ return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unsuccessful response: [response=%s]", response)
}
- return "${" + variableWeAreLookingFor + "}"
+ return tokenDetailsResponse, nil
}
-func SubstituteSecrets(secrets []models.SingleEnvironmentVariable) []models.SingleEnvironmentVariable {
- hashMapOfCompleteVariables := make(map[string]string)
- hashMapOfSelfRefs := make(map[string]string)
- expandedSecrets := []models.SingleEnvironmentVariable{}
-
- for _, secret := range secrets {
- expandedVariable := getExpandedEnvVariable(secrets, secret.Key, hashMapOfCompleteVariables, hashMapOfSelfRefs)
- expandedSecrets = append(expandedSecrets, models.SingleEnvironmentVariable{
- Key: secret.Key,
- Value: expandedVariable,
- })
+func CallGetSecretsV2(httpClient *resty.Client, request GetEncryptedSecretsV2Request) (GetEncryptedSecretsV2Response, error) {
+ var secretsResponse GetEncryptedSecretsV2Response = GetEncryptedSecretsV2Response{}
+ createHttpRequest := httpClient.
+ R().
+ SetResult(&secretsResponse.Secrets).
+ SetQueryParam("environment", request.EnvironmentName).
+ SetHeader("User-Agent", USER_AGENT_NAME)
+ if request.ETag != "" {
+ createHttpRequest.SetHeader("If-None-Match", request.ETag)
}
- return expandedSecrets
+ response, err := createHttpRequest.Get(fmt.Sprintf("%v/v2/secret/workspace/%v", API_HOST_URL, request.WorkspaceId))
+ if err != nil {
+ return GetEncryptedSecretsV2Response{}, fmt.Errorf("CallGetSecretsV2: Unable to complete api request [err=%s]", err)
+ }
+
+ if response.IsError() {
+ return GetEncryptedSecretsV2Response{}, fmt.Errorf("CallGetSecretsV2: Unsuccessful response: [response=%s]", response)
+ }
+
+ if response.StatusCode() == 304 {
+ secretsResponse.Modified = false
+ } else {
+ secretsResponse.Modified = true
+ }
+
+ secretsResponse.ETag = response.Header().Get("etag")
+
+ return secretsResponse, nil
}
diff --git a/k8-operator/packages/api/models.go b/k8-operator/packages/api/models.go
new file mode 100644
index 000000000..a03f9fee7
--- /dev/null
+++ b/k8-operator/packages/api/models.go
@@ -0,0 +1,75 @@
+package api
+
+import "time"
+
+type GetEncryptedWorkspaceKeyRequest struct {
+ WorkspaceId string `json:"workspaceId"`
+}
+
+type GetEncryptedWorkspaceKeyResponse struct {
+ ID string `json:"_id"`
+ EncryptedKey string `json:"encryptedKey"`
+ Nonce string `json:"nonce"`
+ Sender struct {
+ ID string `json:"_id"`
+ Email string `json:"email"`
+ RefreshVersion int `json:"refreshVersion"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+ V int `json:"__v"`
+ FirstName string `json:"firstName"`
+ LastName string `json:"lastName"`
+ PublicKey string `json:"publicKey"`
+ } `json:"sender"`
+ Receiver string `json:"receiver"`
+ Workspace string `json:"workspace"`
+ V int `json:"__v"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+}
+
+type GetEncryptedSecretsV2Request struct {
+ EnvironmentName string `json:"environmentName"`
+ WorkspaceId string `json:"workspaceId"`
+ ETag string `json:"etag,omitempty"`
+}
+
+type GetEncryptedSecretsV2Response struct {
+ Secrets []struct {
+ ID string `json:"_id"`
+ Version int `json:"version"`
+ Workspace string `json:"workspace"`
+ Type string `json:"type"`
+ Environment string `json:"environment"`
+ SecretKeyCiphertext string `json:"secretKeyCiphertext"`
+ SecretKeyIV string `json:"secretKeyIV"`
+ SecretKeyTag string `json:"secretKeyTag"`
+ SecretKeyHash string `json:"secretKeyHash"`
+ SecretValueCiphertext string `json:"secretValueCiphertext"`
+ SecretValueIV string `json:"secretValueIV"`
+ SecretValueTag string `json:"secretValueTag"`
+ SecretValueHash string `json:"secretValueHash"`
+ SecretCommentCiphertext string `json:"secretCommentCiphertext"`
+ SecretCommentIV string `json:"secretCommentIV"`
+ SecretCommentTag string `json:"secretCommentTag"`
+ SecretCommentHash string `json:"secretCommentHash"`
+ V int `json:"__v"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+ User string `json:"user,omitempty"`
+ }
+
+ Modified bool `json:"modified,omitempty"`
+ ETag string `json:"ETag,omitempty"`
+}
+
+type GetServiceTokenDetailsResponse struct {
+ ID string `json:"_id"`
+ Name string `json:"name"`
+ Workspace string `json:"workspace"`
+ Environment string `json:"environment"`
+ User string `json:"user"`
+ EncryptedKey string `json:"encryptedKey"`
+ Iv string `json:"iv"`
+ Tag string `json:"tag"`
+}
diff --git a/k8-operator/packages/api/variables.go b/k8-operator/packages/api/variables.go
new file mode 100644
index 000000000..214b7e1d8
--- /dev/null
+++ b/k8-operator/packages/api/variables.go
@@ -0,0 +1,3 @@
+package api
+
+var API_HOST_URL string = "https://app.infisical.com/api"
diff --git a/k8-operator/packages/models/api.go b/k8-operator/packages/models/api.go
deleted file mode 100644
index 57420dac5..000000000
--- a/k8-operator/packages/models/api.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package models
-
-import "time"
-
-type PullSecretsByInfisicalTokenResponse struct {
- Secrets []struct {
- ID string `json:"_id"`
- Workspace string `json:"workspace"`
- Type string `json:"type"`
- Environment string `json:"environment"`
- SecretKey struct {
- Workspace string `json:"workspace"`
- Ciphertext string `json:"ciphertext"`
- Iv string `json:"iv"`
- Tag string `json:"tag"`
- Hash string `json:"hash"`
- } `json:"secretKey"`
- SecretValue struct {
- Workspace string `json:"workspace"`
- Ciphertext string `json:"ciphertext"`
- Iv string `json:"iv"`
- Tag string `json:"tag"`
- Hash string `json:"hash"`
- } `json:"secretValue"`
- } `json:"secrets"`
- Key struct {
- EncryptedKey string `json:"encryptedKey"`
- Nonce string `json:"nonce"`
- Sender struct {
- PublicKey string `json:"publicKey"`
- } `json:"sender"`
- Receiver struct {
- RefreshVersion int `json:"refreshVersion"`
- ID string `json:"_id"`
- Email string `json:"email"`
- CustomerID string `json:"customerId"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
- V int `json:"__v"`
- FirstName string `json:"firstName"`
- LastName string `json:"lastName"`
- PublicKey string `json:"publicKey"`
- } `json:"receiver"`
- Workspace string `json:"workspace"`
- } `json:"key"`
-}
-
-type SingleEnvironmentVariable struct {
- Key string `json:"key"`
- Value string `json:"value"`
-}
diff --git a/k8-operator/packages/util/secrets.go b/k8-operator/packages/util/secrets.go
new file mode 100644
index 000000000..c68f76250
--- /dev/null
+++ b/k8-operator/packages/util/secrets.go
@@ -0,0 +1,187 @@
+package util
+
+import (
+ "encoding/base64"
+ "fmt"
+ "strings"
+
+ "github.com/Infisical/infisical/k8-operator/packages/api"
+ "github.com/Infisical/infisical/k8-operator/packages/crypto"
+ "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 {
+ Cipher []byte
+ IV []byte
+ Tag []byte
+ Key []byte
+}
+
+func VerifyServiceToken(serviceToken string) (string, error) {
+ serviceTokenParts := strings.SplitN(serviceToken, ".", 4)
+ if len(serviceTokenParts) < 4 {
+ return "", fmt.Errorf("invalid service token entered. Please double check your service token and try again")
+ }
+
+ serviceToken = fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2])
+ return serviceToken, nil
+}
+
+func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsResponse, error) {
+ serviceTokenParts := strings.SplitN(infisicalToken, ".", 4)
+ if len(serviceTokenParts) < 4 {
+ return api.GetServiceTokenDetailsResponse{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
+ }
+
+ serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2])
+
+ httpClient := resty.New()
+ httpClient.SetAuthToken(serviceToken).
+ SetHeader("Accept", "application/json")
+
+ serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient)
+ if err != nil {
+ return api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to get service token details. [err=%v]", err)
+ }
+
+ return serviceTokenDetails, nil
+}
+
+func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string) ([]SingleEnvironmentVariable, api.GetEncryptedSecretsV2Response, error) {
+ serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
+ if len(serviceTokenParts) < 4 {
+ return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
+ }
+
+ serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2])
+
+ httpClient := resty.New()
+ httpClient.SetAuthToken(serviceToken).
+ SetHeader("Accept", "application/json")
+
+ serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient)
+ if err != nil {
+ return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to get service token details. [err=%v]", err)
+ }
+
+ encryptedSecretsResponse, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
+ WorkspaceId: serviceTokenDetails.Workspace,
+ EnvironmentName: serviceTokenDetails.Environment,
+ ETag: etag,
+ })
+
+ if err != nil {
+ return nil, api.GetEncryptedSecretsV2Response{}, err
+ }
+
+ decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag)
+ if err != nil {
+ return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err)
+ }
+
+ plainTextWorkspaceKey, err := crypto.DecryptSymmetric([]byte(serviceTokenParts[3]), decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV)
+ if err != nil {
+ return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to decrypt the required workspace key")
+ }
+
+ plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecretsResponse)
+ if err != nil {
+ return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
+ }
+
+ return plainTextSecrets, encryptedSecretsResponse, nil
+}
+
+func GetBase64DecodedSymmetricEncryptionDetails(key string, cipher string, IV string, tag string) (DecodedSymmetricEncryptionDetails, error) {
+ cipherx, err := base64.StdEncoding.DecodeString(cipher)
+ if err != nil {
+ return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode cipher text [err=%v]", err)
+ }
+
+ keyx, err := base64.StdEncoding.DecodeString(key)
+ if err != nil {
+ return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode key [err=%v]", err)
+ }
+
+ IVx, err := base64.StdEncoding.DecodeString(IV)
+ if err != nil {
+ return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode IV [err=%v]", err)
+ }
+
+ tagx, err := base64.StdEncoding.DecodeString(tag)
+ if err != nil {
+ return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode tag [err=%v]", err)
+ }
+
+ return DecodedSymmetricEncryptionDetails{
+ Key: keyx,
+ Cipher: cipherx,
+ IV: IVx,
+ Tag: tagx,
+ }, nil
+}
+
+func GetPlainTextSecrets(key []byte, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) ([]SingleEnvironmentVariable, error) {
+ plainTextSecrets := []SingleEnvironmentVariable{}
+ for _, secret := range encryptedSecretsResponse.Secrets {
+ // Decrypt key
+ key_iv, err := base64.StdEncoding.DecodeString(secret.SecretKeyIV)
+ if err != nil {
+ return nil, fmt.Errorf("unable to decode secret IV for secret key")
+ }
+
+ key_tag, err := base64.StdEncoding.DecodeString(secret.SecretKeyTag)
+ if err != nil {
+ return nil, fmt.Errorf("unable to decode secret authentication tag for secret key")
+ }
+
+ key_ciphertext, err := base64.StdEncoding.DecodeString(secret.SecretKeyCiphertext)
+ if err != nil {
+ return nil, fmt.Errorf("unable to decode secret cipher text for secret key")
+ }
+
+ plainTextKey, err := crypto.DecryptSymmetric(key, key_ciphertext, key_tag, key_iv)
+ if err != nil {
+ return nil, fmt.Errorf("unable to symmetrically decrypt secret key")
+ }
+
+ // Decrypt value
+ value_iv, err := base64.StdEncoding.DecodeString(secret.SecretValueIV)
+ if err != nil {
+ return nil, fmt.Errorf("unable to decode secret IV for secret value")
+ }
+
+ value_tag, err := base64.StdEncoding.DecodeString(secret.SecretValueTag)
+ if err != nil {
+ return nil, fmt.Errorf("unable to decode secret authentication tag for secret value")
+ }
+
+ value_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretValueCiphertext)
+ if err != nil {
+ return nil, fmt.Errorf("unable to decode secret cipher text for secret key")
+ }
+
+ plainTextValue, err := crypto.DecryptSymmetric(key, value_ciphertext, value_tag, value_iv)
+ if err != nil {
+ return nil, fmt.Errorf("unable to symmetrically decrypt secret value")
+ }
+
+ plainTextSecret := SingleEnvironmentVariable{
+ Key: string(plainTextKey),
+ Value: string(plainTextValue),
+ Type: string(secret.Type),
+ ID: secret.ID,
+ }
+
+ plainTextSecrets = append(plainTextSecrets, plainTextSecret)
+ }
+
+ return plainTextSecrets, nil
+}