mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
Merge branch 'main' of https://github.com/Infisical/infisical
This commit is contained in:
@ -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",
|
||||
|
@ -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: '',
|
||||
|
@ -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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
---
|
||||
|
||||
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>
|
||||
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.
|
||||
</Info>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -2,11 +2,6 @@
|
||||
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
|
||||
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.
|
||||
|
||||
<Info>
|
||||
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.
|
||||
</Info>
|
||||
|
BIN
docs/images/api-key-add.png
Normal file
BIN
docs/images/api-key-add.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 278 KiB |
BIN
docs/images/api-key-dashboard.png
Normal file
BIN
docs/images/api-key-dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 280 KiB |
BIN
docs/images/api-key-settings.png
Normal file
BIN
docs/images/api-key-settings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 249 KiB |
@ -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: ''
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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"`
|
||||
|
@ -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
|
||||
|
26
k8-operator/config/samples/deployment.yaml
Normal file
26
k8-operator/config/samples/deployment.yaml
Normal file
@ -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:
|
||||
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
|
||||
|
7
k8-operator/config/samples/serviceTokenSecret.yaml
Normal file
7
k8-operator/config/samples/serviceTokenSecret.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: service-token
|
||||
type: Opaque
|
||||
data:
|
||||
infisicalToken: <base64 infisical token here>
|
121
k8-operator/controllers/auto_redeployment.go
Normal file
121
k8-operator/controllers/auto_redeployment.go
Normal file
@ -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
|
||||
}
|
95
k8-operator/controllers/conditions.go
Normal file
95
k8-operator/controllers/conditions.go
Normal file
@ -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 (
|
||||
"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)
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
75
k8-operator/packages/api/models.go
Normal file
75
k8-operator/packages/api/models.go
Normal file
@ -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"`
|
||||
}
|
3
k8-operator/packages/api/variables.go
Normal file
3
k8-operator/packages/api/variables.go
Normal file
@ -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"`
|
||||
}
|
187
k8-operator/packages/util/secrets.go
Normal file
187
k8-operator/packages/util/secrets.go
Normal file
@ -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