Skip to content
This repository was archived by the owner on Jul 26, 2022. It is now read-only.

Commit dad820a

Browse files
feat: Akeyless backend (#767)
* Added AkeylessBackend * fix missing spaces * Update akeyless-backend.js * removed key name from akyeless res * Update package-lock.json * fixed secretValue extra stringify * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * fix: update package-lock.json Signed-off-by: Markus Maga <[email protected]> Co-authored-by: renanaAkeyless <[email protected]>
1 parent 96c5f2a commit dad820a

11 files changed

+3423
-286
lines changed

README.md

+32-1
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ A few properties have changed name overtime, we still maintain backwards compatb
445445

446446
## Backends
447447

448-
kubernetes-external-secrets supports AWS Secrets Manager, AWS System Manager, Hashicorp Vault, Azure Key Vault, Google Secret Manager and Alibaba Cloud KMS Secret Manager.
448+
kubernetes-external-secrets supports AWS Secrets Manager, AWS System Manager, Akeyless, Hashicorp Vault, Azure Key Vault, Google Secret Manager and Alibaba Cloud KMS Secret Manager.
449449

450450
### AWS Secrets Manager
451451

@@ -556,6 +556,37 @@ spec:
556556
- path: /extra-people/
557557
recursive: false
558558
```
559+
### Akeyless Vault
560+
561+
kubernetes-external-secrets supports fetching secrets from [Akeyless Vault](https://www.akeyless.io/), .
562+
You will need to set the following environment variables:
563+
564+
```yml
565+
env:
566+
#akeyless rest-v2 endpoint
567+
AKEYLESS_API_ENDPOINT: https://api.akeyless.io
568+
AKEYLESS_ACCESS_ID:
569+
#AKEYLESS_ACCESS_TYPE can be one of the following: aws_iam/azure_ad/gcp/access_key
570+
AKEYLESS_ACCESS_TYPE:
571+
#AKEYLESS_ACCESS_TYPE_PARAM can be one of the following: gcp-audience/azure-obj-id/access-key
572+
#AKEYLESS_ACCESS_TYPE_PARAM:
573+
574+
```
575+
576+
Once you have kubernetes-external-secrets installed, you can create an external secret with YAML like the following:
577+
578+
```yml
579+
apiVersion: 'kubernetes-client.io/v1'
580+
kind: ExternalSecret
581+
metadata:
582+
name: hello-secret
583+
spec:
584+
backendType: akeyless
585+
data:
586+
- key: path/secret-name
587+
name: password
588+
589+
```
559590

560591
### Hashicorp Vault
561592

charts/kubernetes-external-secrets/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ The following table lists the configurable parameters of the `kubernetes-externa
5252
| `env.ROLE_PERMITTED_ANNOTATION` | Specify the annotation key where to lookup the role arn permission boundaries | `iam.amazonaws.com/permitted` |
5353
| `env.POLLER_INTERVAL_MILLISECONDS` | Set POLLER_INTERVAL_MILLISECONDS in Deployment Pod | `10000` |
5454
| `env.VAULT_ADDR` | Endpoint for the Vault backend, if using Vault | `http://127.0.0.1:8200` |
55+
| `env.AKEYLESS_API_ENDPOINT` | Endpoint for the akeyless backend, if using Akeyless | `https://api.akeyless.io` |
56+
| `env.AKEYLESS_ACCESS_ID` | Akeyless access-id , if using Akeyless | `nil` |
57+
| `env.AKEYLESS_ACCESS_TYPE` | Akeyless access-type (aws_iam/gcp/azure_ad/access_key), if using Akeyless | `nil` |
58+
| `env.AKEYLESS_ACCESS_TYPE_PARAM` | Additional parameter per access type, if using Akeyless | `nil` |
5559
| `env.DISABLE_POLLING` | Disables backend polling and only updates secrets when ExternalSecret is modified, setting this to any value will disable polling | `nil` |
5660
| `env.WATCH_TIMEOUT` | Restarts the external secrets resource watcher if no events have been seen in this time period (miliseconds) | `60000` |
5761
| `env.WATCHED_NAMESPACES` | Limits which namespaces the controller will watch, by default all namespaces will be watched. Comma separated list `qa,stage` | `''` |

charts/kubernetes-external-secrets/crds/kubernetes-client.io_externalsecrets_crd.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ spec:
5353
- gcpSecretsManager
5454
- alicloudSecretsManager
5555
- ibmcloudSecretsManager
56+
- akeyless
5657
vaultRole:
5758
description: >-
5859
Used by: vault
@@ -167,6 +168,10 @@ spec:
167168
backendType:
168169
enum:
169170
- ibmcloudSecretsManager
171+
- properties:
172+
backendType:
173+
enum:
174+
- akeyless
170175
anyOf:
171176
- required:
172177
- data

charts/kubernetes-external-secrets/values.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ env:
1111
WATCHED_NAMESPACES: "" # Comma separated list of namespaces, empty or unset means ALL namespaces.
1212
LOG_LEVEL: info
1313
LOG_MESSAGE_KEY: "msg"
14+
15+
#Akeyless rest-v2 endpoint
16+
AKEYLESS_API_ENDPOINT: https://api.akeyless.io
17+
AKEYLESS_ACCESS_ID:
18+
#AKEYLESS_ACCESS_TYPE can be one of the following: aws_iam/azure_ad/gcp/access_key
19+
AKEYLESS_ACCESS_TYPE:
20+
#AKEYLESS_ACCESS_TYPE_PARAM can be one of the following: gcp-audience/azure-obj-id/access-key
21+
#AKEYLESS_ACCESS_TYPE_PARAM:
22+
23+
1424
# Print logs level as string ("info") rather than integer (30)
1525
# USE_HUMAN_READABLE_LOG_LEVELS: true
1626
METRICS_PORT: 3001

config/akeyless-config.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict'
2+
const akeyless = require('akeyless')
3+
const AkeylessClient = new akeyless.ApiClient()
4+
AkeylessClient.basePath = process.env.AKEYLESS_API_ENDPOINT || 'https://api.akeyless.io'
5+
6+
// Akeyless expects the following four environment variables:
7+
// - AKEYLESS_API_ENDPOINT: api-gw endpoint URL http(s)://api.akeyless.io
8+
// - AKEYLESS_ACCESS_ID: The access ID
9+
// - AKEYLESS_ACCESS_TYPE: The access type
10+
// - AKEYLESS_ACCESS_TYPE_PARAM: AZURE_OBJ_ID OR GCP_AUDIENCE OR ACCESS_KEY
11+
12+
const client = new akeyless.V2Api(AkeylessClient)
13+
module.exports = {
14+
credential: {
15+
accessTypeParam: process.env.AKEYLESS_ACCESS_TYPE_PARAM,
16+
accessId: process.env.AKEYLESS_ACCESS_ID,
17+
accessType: process.env.AKEYLESS_ACCESS_TYPE,
18+
client: client
19+
}
20+
}

config/index.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const azureConfig = require('./azure-config')
1313
const alicloudConfig = require('./alicloud-config')
1414
const gcpConfig = require('./gcp-config')
1515
const ibmcloudConfig = require('./ibmcloud-config')
16+
const akeylessConfig = require('./akeyless-config')
1617
const envConfig = require('./environment')
1718
const SecretsManagerBackend = require('../lib/backends/secrets-manager-backend')
1819
const SystemManagerBackend = require('../lib/backends/system-manager-backend')
@@ -21,6 +22,7 @@ const AzureKeyVaultBackend = require('../lib/backends/azure-keyvault-backend')
2122
const GCPSecretsManagerBackend = require('../lib/backends/gcp-secrets-manager-backend')
2223
const AliCloudSecretsManagerBackend = require('../lib/backends/alicloud-secrets-manager-backend')
2324
const IbmCloudSecretsManagerBackend = require('../lib/backends/ibmcloud-secrets-manager-backend')
25+
const AkeylessBackend = require('../lib/backends/akeyless-backend')
2426

2527
// Get document, or throw exception on error
2628
// eslint-disable-next-line security/detect-non-literal-fs-filename
@@ -103,6 +105,10 @@ const ibmcloudSecretsManagerBackend = new IbmCloudSecretsManagerBackend({
103105
credential: ibmcloudConfig.credential,
104106
logger
105107
})
108+
const akeylessBackend = new AkeylessBackend({
109+
credential: akeylessConfig.credential,
110+
logger
111+
})
106112

107113
const backends = {
108114
// when adding a new backend, make sure to change the CRD property too
@@ -112,7 +118,8 @@ const backends = {
112118
azureKeyVault: azureKeyVaultBackend,
113119
gcpSecretsManager: gcpSecretsManagerBackend,
114120
alicloudSecretsManager: alicloudSecretsManagerBackend,
115-
ibmcloudSecretsManager: ibmcloudSecretsManagerBackend
121+
ibmcloudSecretsManager: ibmcloudSecretsManagerBackend,
122+
akeyless: akeylessBackend
116123
}
117124

118125
// backwards compatibility

examples/akeyless-example.yaml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
apiVersion: 'kubernetes-client.io/v1'
2+
kind: ExternalSecret
3+
metadata:
4+
name: hello-secret
5+
spec:
6+
backendType: akeyless
7+
data:
8+
- key: secret-name
9+
name: creds
10+
11+
---
12+
13+
apiVersion: 'kubernetes-client.io/v1'
14+
kind: ExternalSecret
15+
metadata:
16+
name: hello-dynamic-secret
17+
spec:
18+
backendType: akeyless
19+
data:
20+
- key: dynamic-secret-name
21+
name: creds

lib/backends/akeyless-backend.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use strict'
2+
const akeyless = require('akeyless')
3+
const akeylessCloud = require('akeyless-cloud-id')
4+
const KVBackend = require('./kv-backend')
5+
6+
/** Akeyless Secrets Manager backend class. */
7+
class AkeylessBackend extends KVBackend {
8+
/**
9+
* Create Akeyless backend.
10+
* @param {Object} credential - Credentials for authenticating with Akeyless Vault.
11+
* @param {Object} logger - Logger for logging stuff.
12+
*/
13+
constructor ({ credential, logger }) {
14+
super({ logger })
15+
this._credential = credential
16+
}
17+
18+
_getCloudId () {
19+
return new Promise((resolve, reject) => {
20+
akeylessCloud.getCloudId(this._credential.accessType, this._credential.accessTypeParam, (err, res) => {
21+
if (err) {
22+
reject(err)
23+
} else {
24+
resolve(res)
25+
}
26+
})
27+
})
28+
}
29+
30+
async _getSecret (key) {
31+
const api = this._credential.client
32+
const cloudId = await this._getCloudId()
33+
const opts = { 'access-id': this._credential.accessId, 'access-type': this._credential.accessType, 'access-key': this._credential.accessTypeParam, 'cloud-id': cloudId }
34+
35+
const authResult = await api.auth(akeyless.Auth.constructFromObject(opts))
36+
const token = authResult.token
37+
38+
const dataType = await api.describeItem(akeyless.DescribeItem.constructFromObject({
39+
name: key,
40+
token: token
41+
}))
42+
if (dataType.item_type === 'DYNAMIC_SECRET') {
43+
const data = await api.getDynamicSecretValue(akeyless.GetDynamicSecretValue.constructFromObject({
44+
name: key,
45+
token: token
46+
}))
47+
return JSON.stringify(data)
48+
}
49+
if (dataType.item_type === 'STATIC_SECRET') {
50+
const staticSecretParams = akeyless.GetSecretValue.constructFromObject({
51+
names: [key],
52+
token: token
53+
})
54+
const data = await api.getSecretValue(staticSecretParams)
55+
const secretValue = JSON.stringify(data[key])
56+
return JSON.parse(secretValue)
57+
} else {
58+
throw new Error('Invalid secret type' + dataType.item_type)
59+
}
60+
}
61+
62+
/**
63+
* Get secret value from Akeyless Vault.
64+
* @param {string} key - Key the full name (path/name) of the stored secret at Akeyless.
65+
* @returns {Promise} Promise object representing secret property value.
66+
*/
67+
async _get ({ key }) {
68+
this._logger.info(`fetching secret ${key} from akeyless`)
69+
const secret = await this._getSecret(key)
70+
return secret
71+
}
72+
}
73+
74+
module.exports = AkeylessBackend

lib/backends/akeyless-backend.test.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const { expect } = require('chai')
5+
const sinon = require('sinon')
6+
7+
const AkeylessBackend = require('./akeyless-backend')
8+
9+
describe('AkeylessBackend', () => {
10+
let loggerMock
11+
let clientMock
12+
let akeylessBackend
13+
14+
const secret = 'fakeSecretValue'
15+
const key = 'secret_name'
16+
17+
beforeEach(() => {
18+
loggerMock = sinon.mock()
19+
loggerMock.info = sinon.stub()
20+
clientMock = sinon.mock()
21+
clientMock.getSecretValue = sinon.stub().returns({ [key]: secret })
22+
clientMock.getDynamicSecretValue = sinon.stub().returns(secret)
23+
clientMock.auth = sinon.stub().returns('token')
24+
clientMock.describeItem = sinon.stub().returns({ item_type: 'STATIC_SECRET' })
25+
26+
akeylessBackend = new AkeylessBackend({
27+
credential: { endpoint: 'https//sampleendpoint', accessType: 'access_key', client: clientMock },
28+
logger: loggerMock
29+
})
30+
})
31+
32+
describe('_get', () => {
33+
it('returns secret property value', async () => {
34+
const specOptions = {}
35+
const keyOptions = {}
36+
const secretPropertyValue = await akeylessBackend._get({
37+
key: key,
38+
specOptions,
39+
keyOptions
40+
})
41+
expect(secretPropertyValue).equals(secret)
42+
})
43+
})
44+
})

0 commit comments

Comments
 (0)