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

Commit

Permalink
feat: Akeyless backend (#767)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
Flydiverny and renanaAkeyless authored Jun 3, 2021
1 parent 96c5f2a commit dad820a
Show file tree
Hide file tree
Showing 11 changed files with 3,423 additions and 286 deletions.
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ A few properties have changed name overtime, we still maintain backwards compatb

## Backends

kubernetes-external-secrets supports AWS Secrets Manager, AWS System Manager, Hashicorp Vault, Azure Key Vault, Google Secret Manager and Alibaba Cloud KMS Secret Manager.
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.

### AWS Secrets Manager

Expand Down Expand Up @@ -556,6 +556,37 @@ spec:
- path: /extra-people/
recursive: false
```
### Akeyless Vault

kubernetes-external-secrets supports fetching secrets from [Akeyless Vault](https://www.akeyless.io/), .
You will need to set the following environment variables:

```yml
env:
#akeyless rest-v2 endpoint
AKEYLESS_API_ENDPOINT: https://api.akeyless.io
AKEYLESS_ACCESS_ID:
#AKEYLESS_ACCESS_TYPE can be one of the following: aws_iam/azure_ad/gcp/access_key
AKEYLESS_ACCESS_TYPE:
#AKEYLESS_ACCESS_TYPE_PARAM can be one of the following: gcp-audience/azure-obj-id/access-key
#AKEYLESS_ACCESS_TYPE_PARAM:
```

Once you have kubernetes-external-secrets installed, you can create an external secret with YAML like the following:

```yml
apiVersion: 'kubernetes-client.io/v1'
kind: ExternalSecret
metadata:
name: hello-secret
spec:
backendType: akeyless
data:
- key: path/secret-name
name: password
```

### Hashicorp Vault

Expand Down
4 changes: 4 additions & 0 deletions charts/kubernetes-external-secrets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ The following table lists the configurable parameters of the `kubernetes-externa
| `env.ROLE_PERMITTED_ANNOTATION` | Specify the annotation key where to lookup the role arn permission boundaries | `iam.amazonaws.com/permitted` |
| `env.POLLER_INTERVAL_MILLISECONDS` | Set POLLER_INTERVAL_MILLISECONDS in Deployment Pod | `10000` |
| `env.VAULT_ADDR` | Endpoint for the Vault backend, if using Vault | `http://127.0.0.1:8200` |
| `env.AKEYLESS_API_ENDPOINT` | Endpoint for the akeyless backend, if using Akeyless | `https://api.akeyless.io` |
| `env.AKEYLESS_ACCESS_ID` | Akeyless access-id , if using Akeyless | `nil` |
| `env.AKEYLESS_ACCESS_TYPE` | Akeyless access-type (aws_iam/gcp/azure_ad/access_key), if using Akeyless | `nil` |
| `env.AKEYLESS_ACCESS_TYPE_PARAM` | Additional parameter per access type, if using Akeyless | `nil` |
| `env.DISABLE_POLLING` | Disables backend polling and only updates secrets when ExternalSecret is modified, setting this to any value will disable polling | `nil` |
| `env.WATCH_TIMEOUT` | Restarts the external secrets resource watcher if no events have been seen in this time period (miliseconds) | `60000` |
| `env.WATCHED_NAMESPACES` | Limits which namespaces the controller will watch, by default all namespaces will be watched. Comma separated list `qa,stage` | `''` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ spec:
- gcpSecretsManager
- alicloudSecretsManager
- ibmcloudSecretsManager
- akeyless
vaultRole:
description: >-
Used by: vault
Expand Down Expand Up @@ -167,6 +168,10 @@ spec:
backendType:
enum:
- ibmcloudSecretsManager
- properties:
backendType:
enum:
- akeyless
anyOf:
- required:
- data
Expand Down
10 changes: 10 additions & 0 deletions charts/kubernetes-external-secrets/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ env:
WATCHED_NAMESPACES: "" # Comma separated list of namespaces, empty or unset means ALL namespaces.
LOG_LEVEL: info
LOG_MESSAGE_KEY: "msg"

#Akeyless rest-v2 endpoint
AKEYLESS_API_ENDPOINT: https://api.akeyless.io
AKEYLESS_ACCESS_ID:
#AKEYLESS_ACCESS_TYPE can be one of the following: aws_iam/azure_ad/gcp/access_key
AKEYLESS_ACCESS_TYPE:
#AKEYLESS_ACCESS_TYPE_PARAM can be one of the following: gcp-audience/azure-obj-id/access-key
#AKEYLESS_ACCESS_TYPE_PARAM:


# Print logs level as string ("info") rather than integer (30)
# USE_HUMAN_READABLE_LOG_LEVELS: true
METRICS_PORT: 3001
Expand Down
20 changes: 20 additions & 0 deletions config/akeyless-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict'
const akeyless = require('akeyless')
const AkeylessClient = new akeyless.ApiClient()
AkeylessClient.basePath = process.env.AKEYLESS_API_ENDPOINT || 'https://api.akeyless.io'

// Akeyless expects the following four environment variables:
// - AKEYLESS_API_ENDPOINT: api-gw endpoint URL http(s)://api.akeyless.io
// - AKEYLESS_ACCESS_ID: The access ID
// - AKEYLESS_ACCESS_TYPE: The access type
// - AKEYLESS_ACCESS_TYPE_PARAM: AZURE_OBJ_ID OR GCP_AUDIENCE OR ACCESS_KEY

const client = new akeyless.V2Api(AkeylessClient)
module.exports = {
credential: {
accessTypeParam: process.env.AKEYLESS_ACCESS_TYPE_PARAM,
accessId: process.env.AKEYLESS_ACCESS_ID,
accessType: process.env.AKEYLESS_ACCESS_TYPE,
client: client
}
}
9 changes: 8 additions & 1 deletion config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const azureConfig = require('./azure-config')
const alicloudConfig = require('./alicloud-config')
const gcpConfig = require('./gcp-config')
const ibmcloudConfig = require('./ibmcloud-config')
const akeylessConfig = require('./akeyless-config')
const envConfig = require('./environment')
const SecretsManagerBackend = require('../lib/backends/secrets-manager-backend')
const SystemManagerBackend = require('../lib/backends/system-manager-backend')
Expand All @@ -21,6 +22,7 @@ const AzureKeyVaultBackend = require('../lib/backends/azure-keyvault-backend')
const GCPSecretsManagerBackend = require('../lib/backends/gcp-secrets-manager-backend')
const AliCloudSecretsManagerBackend = require('../lib/backends/alicloud-secrets-manager-backend')
const IbmCloudSecretsManagerBackend = require('../lib/backends/ibmcloud-secrets-manager-backend')
const AkeylessBackend = require('../lib/backends/akeyless-backend')

// Get document, or throw exception on error
// eslint-disable-next-line security/detect-non-literal-fs-filename
Expand Down Expand Up @@ -103,6 +105,10 @@ const ibmcloudSecretsManagerBackend = new IbmCloudSecretsManagerBackend({
credential: ibmcloudConfig.credential,
logger
})
const akeylessBackend = new AkeylessBackend({
credential: akeylessConfig.credential,
logger
})

const backends = {
// when adding a new backend, make sure to change the CRD property too
Expand All @@ -112,7 +118,8 @@ const backends = {
azureKeyVault: azureKeyVaultBackend,
gcpSecretsManager: gcpSecretsManagerBackend,
alicloudSecretsManager: alicloudSecretsManagerBackend,
ibmcloudSecretsManager: ibmcloudSecretsManagerBackend
ibmcloudSecretsManager: ibmcloudSecretsManagerBackend,
akeyless: akeylessBackend
}

// backwards compatibility
Expand Down
21 changes: 21 additions & 0 deletions examples/akeyless-example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: 'kubernetes-client.io/v1'
kind: ExternalSecret
metadata:
name: hello-secret
spec:
backendType: akeyless
data:
- key: secret-name
name: creds

---

apiVersion: 'kubernetes-client.io/v1'
kind: ExternalSecret
metadata:
name: hello-dynamic-secret
spec:
backendType: akeyless
data:
- key: dynamic-secret-name
name: creds
74 changes: 74 additions & 0 deletions lib/backends/akeyless-backend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict'
const akeyless = require('akeyless')
const akeylessCloud = require('akeyless-cloud-id')
const KVBackend = require('./kv-backend')

/** Akeyless Secrets Manager backend class. */
class AkeylessBackend extends KVBackend {
/**
* Create Akeyless backend.
* @param {Object} credential - Credentials for authenticating with Akeyless Vault.
* @param {Object} logger - Logger for logging stuff.
*/
constructor ({ credential, logger }) {
super({ logger })
this._credential = credential
}

_getCloudId () {
return new Promise((resolve, reject) => {
akeylessCloud.getCloudId(this._credential.accessType, this._credential.accessTypeParam, (err, res) => {
if (err) {
reject(err)
} else {
resolve(res)
}
})
})
}

async _getSecret (key) {
const api = this._credential.client
const cloudId = await this._getCloudId()
const opts = { 'access-id': this._credential.accessId, 'access-type': this._credential.accessType, 'access-key': this._credential.accessTypeParam, 'cloud-id': cloudId }

const authResult = await api.auth(akeyless.Auth.constructFromObject(opts))
const token = authResult.token

const dataType = await api.describeItem(akeyless.DescribeItem.constructFromObject({
name: key,
token: token
}))
if (dataType.item_type === 'DYNAMIC_SECRET') {
const data = await api.getDynamicSecretValue(akeyless.GetDynamicSecretValue.constructFromObject({
name: key,
token: token
}))
return JSON.stringify(data)
}
if (dataType.item_type === 'STATIC_SECRET') {
const staticSecretParams = akeyless.GetSecretValue.constructFromObject({
names: [key],
token: token
})
const data = await api.getSecretValue(staticSecretParams)
const secretValue = JSON.stringify(data[key])
return JSON.parse(secretValue)
} else {
throw new Error('Invalid secret type' + dataType.item_type)
}
}

/**
* Get secret value from Akeyless Vault.
* @param {string} key - Key the full name (path/name) of the stored secret at Akeyless.
* @returns {Promise} Promise object representing secret property value.
*/
async _get ({ key }) {
this._logger.info(`fetching secret ${key} from akeyless`)
const secret = await this._getSecret(key)
return secret
}
}

module.exports = AkeylessBackend
44 changes: 44 additions & 0 deletions lib/backends/akeyless-backend.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-env mocha */
'use strict'

const { expect } = require('chai')
const sinon = require('sinon')

const AkeylessBackend = require('./akeyless-backend')

describe('AkeylessBackend', () => {
let loggerMock
let clientMock
let akeylessBackend

const secret = 'fakeSecretValue'
const key = 'secret_name'

beforeEach(() => {
loggerMock = sinon.mock()
loggerMock.info = sinon.stub()
clientMock = sinon.mock()
clientMock.getSecretValue = sinon.stub().returns({ [key]: secret })
clientMock.getDynamicSecretValue = sinon.stub().returns(secret)
clientMock.auth = sinon.stub().returns('token')
clientMock.describeItem = sinon.stub().returns({ item_type: 'STATIC_SECRET' })

akeylessBackend = new AkeylessBackend({
credential: { endpoint: 'https//sampleendpoint', accessType: 'access_key', client: clientMock },
logger: loggerMock
})
})

describe('_get', () => {
it('returns secret property value', async () => {
const specOptions = {}
const keyOptions = {}
const secretPropertyValue = await akeylessBackend._get({
key: key,
specOptions,
keyOptions
})
expect(secretPropertyValue).equals(secret)
})
})
})
Loading

0 comments on commit dad820a

Please sign in to comment.