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

feat: Akeyless backend #767

Merged
merged 15 commits into from
Jun 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -176,6 +177,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