mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 22:28:15 +00:00
Merge branch 'main' of https://github.com/Infisical/infisical
This commit is contained in:
+32
-32
@@ -3582,39 +3582,39 @@
|
|||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "[email protected]"
|
||||||
},
|
},
|
||||||
"firstName": {
|
"firstName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "John"
|
||||||
},
|
},
|
||||||
"lastName": {
|
"lastName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "Doe"
|
||||||
},
|
},
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "johns_nacl_public_key"
|
||||||
},
|
},
|
||||||
"encryptedPrivateKey": {
|
"encryptedPrivateKey": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "johns_enc_nacl_private_key"
|
||||||
},
|
},
|
||||||
"iv": {
|
"iv": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "iv_of_enc_nacl_private_key"
|
||||||
},
|
},
|
||||||
"tag": {
|
"tag": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "tag_of_enc_nacl_private_key"
|
||||||
},
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "2023-01-13T14:16:12.210Z"
|
||||||
},
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "2023-01-13T14:16:12.210Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3630,27 +3630,27 @@
|
|||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "[email protected]"
|
||||||
},
|
},
|
||||||
"firstName": {
|
"firstName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "John"
|
||||||
},
|
},
|
||||||
"lastName": {
|
"lastName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "Doe"
|
||||||
},
|
},
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "johns_nacl_public_key"
|
||||||
},
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "2023-01-13T14:16:12.210Z"
|
||||||
},
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "2023-01-13T14:16:12.210Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3676,27 +3676,27 @@
|
|||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "[email protected]"
|
||||||
},
|
},
|
||||||
"firstName": {
|
"firstName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "John"
|
||||||
},
|
},
|
||||||
"lastName": {
|
"lastName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "Doe"
|
||||||
},
|
},
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "johns_nacl_public_key"
|
||||||
},
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "2023-01-13T14:16:12.210Z"
|
||||||
},
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "2023-01-13T14:16:12.210Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3723,7 +3723,7 @@
|
|||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "Acme Corp."
|
||||||
},
|
},
|
||||||
"customerId": {
|
"customerId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -3776,7 +3776,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "senders_nacl_public_key"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3938,11 +3938,11 @@
|
|||||||
},
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "2023-01-13T14:16:12.210Z"
|
||||||
},
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "2023-01-13T14:16:12.210Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3962,15 +3962,15 @@
|
|||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "[email protected]"
|
||||||
},
|
},
|
||||||
"firstName": {
|
"firstName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "John"
|
||||||
},
|
},
|
||||||
"lastName": {
|
"lastName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "Doe"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4033,11 +4033,11 @@
|
|||||||
},
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "2023-01-13T14:16:12.210Z"
|
||||||
},
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "2023-01-13T14:16:12.210Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4087,7 +4087,7 @@
|
|||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "shared"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -4095,7 +4095,7 @@
|
|||||||
},
|
},
|
||||||
"environment": {
|
"environment": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": ""
|
"example": "dev"
|
||||||
},
|
},
|
||||||
"isDeleted": {
|
"isDeleted": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
+32
-33
@@ -2,7 +2,6 @@
|
|||||||
const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' });
|
const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' });
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
const { secretSchema } = require('./schemas/index.ts');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates OpenAPI specs for all Infisical API endpoints:
|
* Generates OpenAPI specs for all Infisical API endpoints:
|
||||||
@@ -43,25 +42,25 @@ const generateOpenAPISpec = async () => {
|
|||||||
definitions: {
|
definitions: {
|
||||||
CurrentUser: {
|
CurrentUser: {
|
||||||
_id: '',
|
_id: '',
|
||||||
email: '',
|
email: '[email protected]',
|
||||||
firstName: '',
|
firstName: 'John',
|
||||||
lastName: '',
|
lastName: 'Doe',
|
||||||
publicKey: '',
|
publicKey: 'johns_nacl_public_key',
|
||||||
encryptedPrivateKey: '',
|
encryptedPrivateKey: 'johns_enc_nacl_private_key',
|
||||||
iv: '',
|
iv: 'iv_of_enc_nacl_private_key',
|
||||||
tag: '',
|
tag: 'tag_of_enc_nacl_private_key',
|
||||||
updatedAt: '',
|
updatedAt: '2023-01-13T14:16:12.210Z',
|
||||||
createdAt: ''
|
createdAt: '2023-01-13T14:16:12.210Z'
|
||||||
},
|
},
|
||||||
Membership: {
|
Membership: {
|
||||||
user: {
|
user: {
|
||||||
_id: '',
|
_id: '',
|
||||||
email: '',
|
email: '[email protected]',
|
||||||
firstName: '',
|
firstName: 'John',
|
||||||
lastName: '',
|
lastName: 'Doe',
|
||||||
publicKey: '',
|
publicKey: 'johns_nacl_public_key',
|
||||||
updatedAt: '',
|
updatedAt: '2023-01-13T14:16:12.210Z',
|
||||||
createdAt: ''
|
createdAt: '2023-01-13T14:16:12.210Z'
|
||||||
},
|
},
|
||||||
workspace: '',
|
workspace: '',
|
||||||
role: 'admin'
|
role: 'admin'
|
||||||
@@ -69,12 +68,12 @@ const generateOpenAPISpec = async () => {
|
|||||||
MembershipOrg: {
|
MembershipOrg: {
|
||||||
user: {
|
user: {
|
||||||
_id: '',
|
_id: '',
|
||||||
email: '',
|
email: '[email protected]',
|
||||||
firstName: '',
|
firstName: 'John',
|
||||||
lastName: '',
|
lastName: 'Doe',
|
||||||
publicKey: '',
|
publicKey: 'johns_nacl_public_key',
|
||||||
updatedAt: '',
|
updatedAt: '2023-01-13T14:16:12.210Z',
|
||||||
createdAt: ''
|
createdAt: '2023-01-13T14:16:12.210Z'
|
||||||
},
|
},
|
||||||
organization: '',
|
organization: '',
|
||||||
role: 'owner',
|
role: 'owner',
|
||||||
@@ -82,7 +81,7 @@ const generateOpenAPISpec = async () => {
|
|||||||
},
|
},
|
||||||
Organization: {
|
Organization: {
|
||||||
_id: '',
|
_id: '',
|
||||||
name: '',
|
name: 'Acme Corp.',
|
||||||
customerId: ''
|
customerId: ''
|
||||||
},
|
},
|
||||||
Project: {
|
Project: {
|
||||||
@@ -97,7 +96,7 @@ const generateOpenAPISpec = async () => {
|
|||||||
encryptedkey: '',
|
encryptedkey: '',
|
||||||
nonce: '',
|
nonce: '',
|
||||||
sender: {
|
sender: {
|
||||||
publicKey: ''
|
publicKey: 'senders_nacl_public_key'
|
||||||
},
|
},
|
||||||
receiver: '',
|
receiver: '',
|
||||||
workspace: ''
|
workspace: ''
|
||||||
@@ -141,16 +140,16 @@ const generateOpenAPISpec = async () => {
|
|||||||
secretCommentCiphertext: '',
|
secretCommentCiphertext: '',
|
||||||
secretCommentIV: '',
|
secretCommentIV: '',
|
||||||
secretCommentTag: '',
|
secretCommentTag: '',
|
||||||
updatedAt: '',
|
updatedAt: '2023-01-13T14:16:12.210Z',
|
||||||
createdAt: ''
|
createdAt: '2023-01-13T14:16:12.210Z'
|
||||||
},
|
},
|
||||||
Log: {
|
Log: {
|
||||||
_id: '',
|
_id: '',
|
||||||
user: {
|
user: {
|
||||||
_id: '',
|
_id: '',
|
||||||
email: '',
|
email: '[email protected]',
|
||||||
firstName: '',
|
firstName: 'John',
|
||||||
lastName: ''
|
lastName: 'Doe'
|
||||||
},
|
},
|
||||||
workspace: '',
|
workspace: '',
|
||||||
actionNames: [
|
actionNames: [
|
||||||
@@ -171,8 +170,8 @@ const generateOpenAPISpec = async () => {
|
|||||||
],
|
],
|
||||||
channel: 'cli',
|
channel: 'cli',
|
||||||
ipAddress: '192.168.0.1',
|
ipAddress: '192.168.0.1',
|
||||||
updatedAt: '',
|
updatedAt: '2023-01-13T14:16:12.210Z',
|
||||||
createdAt: ''
|
createdAt: '2023-01-13T14:16:12.210Z'
|
||||||
},
|
},
|
||||||
SecretSnapshot: {
|
SecretSnapshot: {
|
||||||
workspace: '',
|
workspace: '',
|
||||||
@@ -188,9 +187,9 @@ const generateOpenAPISpec = async () => {
|
|||||||
secret: '',
|
secret: '',
|
||||||
version: 1,
|
version: 1,
|
||||||
workspace: '',
|
workspace: '',
|
||||||
type: '',
|
type: 'shared',
|
||||||
user: '',
|
user: '',
|
||||||
environment: '',
|
environment: 'dev',
|
||||||
isDeleted: '',
|
isDeleted: '',
|
||||||
secretKeyCiphertext: '',
|
secretKeyCiphertext: '',
|
||||||
secretKeyIV: '',
|
secretKeyIV: '',
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
||||||
const secretSchema = require('./secretSchema.ts');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
secretSchema
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
const secretSchema = {
|
|
||||||
_id: {
|
|
||||||
type: 'string',
|
|
||||||
format: 'objectId'
|
|
||||||
},
|
|
||||||
version: {
|
|
||||||
type: 'number'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = secretSchema;
|
|
||||||
@@ -2,3 +2,10 @@
|
|||||||
title: "Create"
|
title: "Create"
|
||||||
openapi: "POST /api/v2/secrets/"
|
openapi: "POST /api/v2/secrets/"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
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).
|
||||||
|
</Tip>
|
||||||
|
|||||||
@@ -2,3 +2,10 @@
|
|||||||
title: "Retrieve"
|
title: "Retrieve"
|
||||||
openapi: "GET /api/v2/secrets/"
|
openapi: "GET /api/v2/secrets/"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
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).
|
||||||
|
</Tip>
|
||||||
|
|||||||
@@ -2,3 +2,10 @@
|
|||||||
title: "Update"
|
title: "Update"
|
||||||
openapi: "PATCH /api/v2/secrets/"
|
openapi: "PATCH /api/v2/secrets/"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
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).
|
||||||
|
</Tip>
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
title: "Authentication"
|
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
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
It's important to keep your API key secure, as it grants access to your
|
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
|
secrets in Infisical. For added security, set a reasonable expiration time and
|
||||||
regular basis.
|
rotate your API key on a regular basis.
|
||||||
</Info>
|
</Info>
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ Prerequisites:
|
|||||||
|
|
||||||
## Flow
|
## 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.
|
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.
|
4. Decrypt the (encrypted) project key with your private key.
|
||||||
5. Encrypt your secret(s) with the project 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
|
## Example
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ Prerequisites:
|
|||||||
|
|
||||||
## Flow
|
## 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.
|
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.
|
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
|
6. Decrypt the (encrypted) secrets
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ Prerequisites:
|
|||||||
|
|
||||||
## Flow
|
## 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.
|
2. Decrypt your (encrypted) private key with your password.
|
||||||
3. Get the project key for the project.
|
3. [Get the (encrypted) project key for the project.](/api-reference/endpoints/workspaces/workspace-key)
|
||||||
4. Decrypt the project key with your private key.
|
4. Decrypt the (encrypted) project key with your private key.
|
||||||
5. Encrypt your secret(s) with the project 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
|
## Example
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
title: "Introduction"
|
title: "Introduction"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Warning>
|
|
||||||
Infisical's REST API is currently unavailable and scheduled to go live on Jan
|
|
||||||
16!
|
|
||||||
</Warning>
|
|
||||||
|
|
||||||
Infisical's REST API provides users an alternative way to programmatically access and manage
|
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
|
secrets via HTTPS requests. This can be useful for automating tasks, such as
|
||||||
rotating credentials, or for integrating secret management into a larger system.
|
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 uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations.
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
Infisical's system ensures greater security such that secrets are
|
Infisical's system requires that secrets be encrypted/decrypted on the
|
||||||
encrypted/decrypted on the client-side but requires users to properly
|
client-side to maintain E2EE. We strongly recommend you read up on the system
|
||||||
implement cryptographic operations to maintain end-to-end encryption (E2EE).
|
prior to using the Infisical API. The (opt-in) ability to retrieve secrets
|
||||||
We're
|
back in decrypted format if you choose to share secrets with Infisical is on
|
||||||
|
our roadmap.
|
||||||
</Info>
|
</Info>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 278 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 280 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 249 KiB |
+32
-32
@@ -2208,31 +2208,31 @@ components:
|
|||||||
example: ''
|
example: ''
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: [email protected]
|
||||||
firstName:
|
firstName:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: John
|
||||||
lastName:
|
lastName:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: Doe
|
||||||
publicKey:
|
publicKey:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: johns_nacl_public_key
|
||||||
encryptedPrivateKey:
|
encryptedPrivateKey:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: johns_enc_nacl_private_key
|
||||||
iv:
|
iv:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: iv_of_enc_nacl_private_key
|
||||||
tag:
|
tag:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: tag_of_enc_nacl_private_key
|
||||||
updatedAt:
|
updatedAt:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
createdAt:
|
createdAt:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
Membership:
|
Membership:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -2244,22 +2244,22 @@ components:
|
|||||||
example: ''
|
example: ''
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: [email protected]
|
||||||
firstName:
|
firstName:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: John
|
||||||
lastName:
|
lastName:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: Doe
|
||||||
publicKey:
|
publicKey:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: johns_nacl_public_key
|
||||||
updatedAt:
|
updatedAt:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
createdAt:
|
createdAt:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
workspace:
|
workspace:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: ''
|
||||||
@@ -2277,22 +2277,22 @@ components:
|
|||||||
example: ''
|
example: ''
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: [email protected]
|
||||||
firstName:
|
firstName:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: John
|
||||||
lastName:
|
lastName:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: Doe
|
||||||
publicKey:
|
publicKey:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: johns_nacl_public_key
|
||||||
updatedAt:
|
updatedAt:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
createdAt:
|
createdAt:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
organization:
|
organization:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: ''
|
||||||
@@ -2310,7 +2310,7 @@ components:
|
|||||||
example: ''
|
example: ''
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: Acme Corp.
|
||||||
customerId:
|
customerId:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: ''
|
||||||
@@ -2348,7 +2348,7 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
publicKey:
|
publicKey:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: senders_nacl_public_key
|
||||||
receiver:
|
receiver:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: ''
|
||||||
@@ -2466,10 +2466,10 @@ components:
|
|||||||
example: ''
|
example: ''
|
||||||
updatedAt:
|
updatedAt:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
createdAt:
|
createdAt:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
Log:
|
Log:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -2484,13 +2484,13 @@ components:
|
|||||||
example: ''
|
example: ''
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: [email protected]
|
||||||
firstName:
|
firstName:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: John
|
||||||
lastName:
|
lastName:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: Doe
|
||||||
workspace:
|
workspace:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: ''
|
||||||
@@ -2533,10 +2533,10 @@ components:
|
|||||||
example: 192.168.0.1
|
example: 192.168.0.1
|
||||||
updatedAt:
|
updatedAt:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
createdAt:
|
createdAt:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: '2023-01-13T14:16:12.210Z'
|
||||||
SecretSnapshot:
|
SecretSnapshot:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -2571,13 +2571,13 @@ components:
|
|||||||
example: ''
|
example: ''
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: shared
|
||||||
user:
|
user:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: ''
|
||||||
environment:
|
environment:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: dev
|
||||||
isDeleted:
|
isDeleted:
|
||||||
type: string
|
type: string
|
||||||
example: ''
|
example: ''
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ 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.0
|
version: 0.1.1
|
||||||
# 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.
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
description: InfisicalSecretSpec defines the desired state of InfisicalSecret
|
description: InfisicalSecretSpec defines the desired state of InfisicalSecret
|
||||||
properties:
|
properties:
|
||||||
environment:
|
|
||||||
description: The Infisical environment such as dev, prod, testing
|
|
||||||
type: string
|
|
||||||
hostAPI:
|
hostAPI:
|
||||||
default: https://app.infisical.com/api
|
default: https://app.infisical.com/api
|
||||||
description: Infisical host to pull secrets from
|
description: Infisical host to pull secrets from
|
||||||
@@ -54,9 +51,6 @@ spec:
|
|||||||
- secretName
|
- secretName
|
||||||
- secretNamespace
|
- secretNamespace
|
||||||
type: object
|
type: object
|
||||||
projectId:
|
|
||||||
description: The Infisical project id
|
|
||||||
type: string
|
|
||||||
tokenSecretReference:
|
tokenSecretReference:
|
||||||
properties:
|
properties:
|
||||||
secretName:
|
secretName:
|
||||||
@@ -69,9 +63,6 @@ spec:
|
|||||||
- secretName
|
- secretName
|
||||||
- secretNamespace
|
- secretNamespace
|
||||||
type: object
|
type: object
|
||||||
required:
|
|
||||||
- environment
|
|
||||||
- projectId
|
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
|
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
|
||||||
|
|||||||
@@ -16,17 +16,11 @@ type KubeSecretReference struct {
|
|||||||
|
|
||||||
// InfisicalSecretSpec defines the desired state of InfisicalSecret
|
// InfisicalSecretSpec defines the desired state of InfisicalSecret
|
||||||
type InfisicalSecretSpec struct {
|
type InfisicalSecretSpec struct {
|
||||||
TokenSecretReference KubeSecretReference `json:"tokenSecretReference,omitempty"`
|
// +kubebuilder:validation:Required
|
||||||
|
TokenSecretReference KubeSecretReference `json:"tokenSecretReference,omitempty"`
|
||||||
|
// +kubebuilder:validation:Required
|
||||||
ManagedSecretReference KubeSecretReference `json:"managedSecretReference,omitempty"`
|
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
|
// Infisical host to pull secrets from
|
||||||
// +kubebuilder:default="https://app.infisical.com/api"
|
// +kubebuilder:default="https://app.infisical.com/api"
|
||||||
HostAPI string `json:"hostAPI,omitempty"`
|
HostAPI string `json:"hostAPI,omitempty"`
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
description: InfisicalSecretSpec defines the desired state of InfisicalSecret
|
description: InfisicalSecretSpec defines the desired state of InfisicalSecret
|
||||||
properties:
|
properties:
|
||||||
environment:
|
|
||||||
description: The Infisical environment such as dev, prod, testing
|
|
||||||
type: string
|
|
||||||
hostAPI:
|
hostAPI:
|
||||||
default: https://app.infisical.com/api
|
default: https://app.infisical.com/api
|
||||||
description: Infisical host to pull secrets from
|
description: Infisical host to pull secrets from
|
||||||
@@ -54,9 +51,6 @@ spec:
|
|||||||
- secretName
|
- secretName
|
||||||
- secretNamespace
|
- secretNamespace
|
||||||
type: object
|
type: object
|
||||||
projectId:
|
|
||||||
description: The Infisical project id
|
|
||||||
type: string
|
|
||||||
tokenSecretReference:
|
tokenSecretReference:
|
||||||
properties:
|
properties:
|
||||||
secretName:
|
secretName:
|
||||||
@@ -69,9 +63,6 @@ spec:
|
|||||||
- secretName
|
- secretName
|
||||||
- secretNamespace
|
- secretNamespace
|
||||||
type: object
|
type: object
|
||||||
required:
|
|
||||||
- environment
|
|
||||||
- projectId
|
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
|
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -3,11 +3,10 @@ kind: InfisicalSecret
|
|||||||
metadata:
|
metadata:
|
||||||
name: infisicalsecret-sample
|
name: infisicalsecret-sample
|
||||||
spec:
|
spec:
|
||||||
projectId: 62faf98ae0b05e8529b5da46
|
hostAPI: https://app.infisical.com/api
|
||||||
environment: dev
|
|
||||||
tokenSecretReference:
|
tokenSecretReference:
|
||||||
secretName: service-token
|
secretName: service-token
|
||||||
secretNamespace: first-project
|
secretNamespace: default
|
||||||
managedSecretReference:
|
managedSecretReference:
|
||||||
secretName: managed-secret
|
secretName: managed-secret
|
||||||
secretNamespace: first-project
|
secretNamespace: default
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: service-token
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
infisicalToken: <base64 infisical token here>
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,16 +2,17 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
|
||||||
|
|
||||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||||
secretsv1alpha1 "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
|
// InfisicalSecretReconciler reconciles a InfisicalSecret object
|
||||||
@@ -31,19 +32,18 @@ type InfisicalSecretReconciler struct {
|
|||||||
// For more details, check Reconcile and its Result here:
|
// For more details, check Reconcile and its Result here:
|
||||||
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
|
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
|
||||||
func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
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
|
var infisicalSecretCR v1alpha1.InfisicalSecret
|
||||||
|
|
||||||
err := r.Get(ctx, req.NamespacedName, &infisicalSecretCR)
|
err := r.Get(ctx, req.NamespacedName, &infisicalSecretCR)
|
||||||
|
|
||||||
requeueTime := time.Minute * 5
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
log.Info("Infisical Secret not found")
|
fmt.Printf("Infisical Secret CRD not found [err=%v]", err)
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{
|
||||||
|
Requeue: false,
|
||||||
|
}, nil
|
||||||
} else {
|
} 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{
|
return ctrl.Result{
|
||||||
RequeueAfter: requeueTime,
|
RequeueAfter: requeueTime,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -52,13 +52,28 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
|||||||
|
|
||||||
// Check if the resource is already marked for deletion
|
// Check if the resource is already marked for deletion
|
||||||
if infisicalSecretCR.GetDeletionTimestamp() != nil {
|
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)
|
err = r.ReconcileInfisicalSecret(ctx, infisicalSecretCR)
|
||||||
r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCR, err)
|
r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCR, err)
|
||||||
|
|
||||||
if err != nil {
|
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{
|
return ctrl.Result{
|
||||||
RequeueAfter: requeueTime,
|
RequeueAfter: requeueTime,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -73,6 +88,6 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
|||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
func (r *InfisicalSecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *InfisicalSecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
For(&secretsv1alpha1.InfisicalSecret{}). // TODO we should also be watching secrets with the name specifed
|
For(&secretsv1alpha1.InfisicalSecret{}).
|
||||||
Complete(r)
|
Complete(r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||||
api "github.com/Infisical/infisical/k8-operator/packages/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/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"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Context, namespacedName types.NamespacedName) (*corev1.Secret, error) {
|
func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Context, namespacedName types.NamespacedName) (*corev1.Secret, error) {
|
||||||
kubeSecret := &corev1.Secret{}
|
kubeSecret := &corev1.Secret{}
|
||||||
@@ -27,14 +27,14 @@ func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Co
|
|||||||
return kubeSecret, err
|
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{
|
tokenSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
||||||
Namespace: infisicalSecret.Spec.TokenSecretReference.SecretNamespace,
|
Namespace: infisicalSecret.Spec.TokenSecretReference.SecretNamespace,
|
||||||
Name: infisicalSecret.Spec.TokenSecretReference.SecretName,
|
Name: infisicalSecret.Spec.TokenSecretReference.SecretName,
|
||||||
})
|
})
|
||||||
|
|
||||||
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.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]
|
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
|
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)
|
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
|
||||||
@@ -56,6 +56,9 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context
|
|||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
|
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
|
||||||
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
|
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
SECRET_VERSION_ANNOTATION: encryptedSecretsResponse.ETag,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Type: "Opaque",
|
Type: "Opaque",
|
||||||
Data: plainProcessedSecrets,
|
Data: plainProcessedSecrets,
|
||||||
@@ -70,13 +73,17 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context
|
|||||||
return nil
|
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)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
managedKubeSecret.Data = plainProcessedSecrets
|
managedKubeSecret.Data = plainProcessedSecrets
|
||||||
|
managedKubeSecret.ObjectMeta.Annotations = map[string]string{
|
||||||
|
SECRET_VERSION_ANNOTATION: encryptedSecretsResponse.ETag,
|
||||||
|
}
|
||||||
|
|
||||||
err := r.Client.Update(ctx, &managedKubeSecret)
|
err := r.Client.Update(ctx, &managedKubeSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to update Kubernetes secret because [%w]", err)
|
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 {
|
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)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look for managed secret by name and namespace
|
||||||
managedKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
managedKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
||||||
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
|
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
|
||||||
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
|
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)
|
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 {
|
if managedKubeSecret != nil {
|
||||||
return err
|
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 {
|
if managedKubeSecret == nil {
|
||||||
return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, secretsFromApi)
|
return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, fullEncryptedSecretsResponse)
|
||||||
} else {
|
} 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -43,9 +43,6 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
description: InfisicalSecretSpec defines the desired state of InfisicalSecret
|
description: InfisicalSecretSpec defines the desired state of InfisicalSecret
|
||||||
properties:
|
properties:
|
||||||
environment:
|
|
||||||
description: The Infisical environment such as dev, prod, testing
|
|
||||||
type: string
|
|
||||||
hostAPI:
|
hostAPI:
|
||||||
default: https://app.infisical.com/api
|
default: https://app.infisical.com/api
|
||||||
description: Infisical host to pull secrets from
|
description: Infisical host to pull secrets from
|
||||||
@@ -62,9 +59,6 @@ spec:
|
|||||||
- secretName
|
- secretName
|
||||||
- secretNamespace
|
- secretNamespace
|
||||||
type: object
|
type: object
|
||||||
projectId:
|
|
||||||
description: The Infisical project id
|
|
||||||
type: string
|
|
||||||
tokenSecretReference:
|
tokenSecretReference:
|
||||||
properties:
|
properties:
|
||||||
secretName:
|
secretName:
|
||||||
@@ -77,9 +71,6 @@ spec:
|
|||||||
- secretName
|
- secretName
|
||||||
- secretNamespace
|
- secretNamespace
|
||||||
type: object
|
type: object
|
||||||
required:
|
|
||||||
- environment
|
|
||||||
- projectId
|
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
|
description: InfisicalSecretStatus defines the observed state of InfisicalSecret
|
||||||
|
|||||||
+50
-147
@@ -1,177 +1,80 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"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"
|
"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) {
|
const USER_AGENT_NAME = "k8-operator"
|
||||||
envsFromApi, err := GetSecretsFromAPIUsingInfisicalToken(infisicalToken, envName, projectId, hostAPI)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return SubstituteSecrets(envsFromApi), nil
|
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
|
||||||
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
|
|
||||||
response, err := httpClient.
|
response, err := httpClient.
|
||||||
R().
|
R().
|
||||||
SetQueryParam("environment", envName).
|
SetResult(&result).
|
||||||
SetQueryParam("channel", "cli").
|
SetHeader("User-Agent", USER_AGENT_NAME).
|
||||||
SetResult(&pullSecretsByInfisicalTokenResponse).
|
Get(endpoint)
|
||||||
Get(fmt.Sprintf("%v/v1/secret/%v/service-token", hostAPI, projectId))
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode() > 299 {
|
if response.StatusCode() > 299 {
|
||||||
return nil, fmt.Errorf(response.Status())
|
return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unsuccessful response: [response=%s]", response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get workspace key
|
return result, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExpandedEnvVariable(secrets []models.SingleEnvironmentVariable, variableWeAreLookingFor string, hashMapOfCompleteVariables map[string]string, hashMapOfSelfRefs map[string]string) string {
|
func CallGetServiceTokenDetailsV2(httpClient *resty.Client) (GetServiceTokenDetailsResponse, error) {
|
||||||
if value, found := hashMapOfCompleteVariables[variableWeAreLookingFor]; found {
|
var tokenDetailsResponse GetServiceTokenDetailsResponse
|
||||||
return value
|
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 response.IsError() {
|
||||||
if secret.Key == variableWeAreLookingFor {
|
return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unsuccessful response: [response=%s]", response)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "${" + variableWeAreLookingFor + "}"
|
return tokenDetailsResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SubstituteSecrets(secrets []models.SingleEnvironmentVariable) []models.SingleEnvironmentVariable {
|
func CallGetSecretsV2(httpClient *resty.Client, request GetEncryptedSecretsV2Request) (GetEncryptedSecretsV2Response, error) {
|
||||||
hashMapOfCompleteVariables := make(map[string]string)
|
var secretsResponse GetEncryptedSecretsV2Response = GetEncryptedSecretsV2Response{}
|
||||||
hashMapOfSelfRefs := make(map[string]string)
|
createHttpRequest := httpClient.
|
||||||
expandedSecrets := []models.SingleEnvironmentVariable{}
|
R().
|
||||||
|
SetResult(&secretsResponse.Secrets).
|
||||||
for _, secret := range secrets {
|
SetQueryParam("environment", request.EnvironmentName).
|
||||||
expandedVariable := getExpandedEnvVariable(secrets, secret.Key, hashMapOfCompleteVariables, hashMapOfSelfRefs)
|
SetHeader("User-Agent", USER_AGENT_NAME)
|
||||||
expandedSecrets = append(expandedSecrets, models.SingleEnvironmentVariable{
|
|
||||||
Key: secret.Key,
|
|
||||||
Value: expandedVariable,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
var API_HOST_URL string = "https://app.infisical.com/api"
|
||||||
@@ -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"`
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user