Skip to content
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
2 changes: 1 addition & 1 deletion plugins/argocd/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function getCustomK8sApi(): CustomObjectsApi {
export async function updateZoneValues(zone: ZoneObject, apis: HookPayloadApis<ZoneObject> | HookPayloadApis<ClusterObject>) {
const { gitlab, vault } = apis
const values = {
vault: await vault.getCredentials(),
vault: await vault.getValues(),
clusters: zone.clusterNames,
}
const zoneRepo = await gitlab.getOrCreateInfraProject(zone.slug)
Expand Down
131 changes: 85 additions & 46 deletions plugins/vault/src/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import axios from 'axios'
import type { ProjectLite } from '@cpn-console/hooks'
import { PluginApi } from '@cpn-console/hooks'
import getConfig from './config.js'
import { generateKVConfigUpdate, getAuthMethod, isAppRoleEnabled } from './utils.js'
import {
generateKVConfigUpdate,
getAuthMethod,
isAppRoleEnabled,
} from './utils.js'

interface ReadOptions {
throwIfNoEntry: boolean
Expand All @@ -15,7 +19,7 @@ export interface AppRoleCredentials {
secretId: string
}

export class VaultApi extends PluginApi {
abstract class VaultApi extends PluginApi {
protected readonly axios: AxiosInstance
private token: string | undefined = undefined

Expand All @@ -31,15 +35,14 @@ export class VaultApi extends PluginApi {

protected async getToken() {
if (!this.token) {
this.token = (await this.axios.post('/v1/auth/token/create'))
.data.auth.client_token as string
this.token = (await this.axios.post('/v1/auth/token/create')).data.auth
.client_token as string
}
return this.token
}

protected async destroy(path: string, kvName: string) {
if (path.startsWith('/'))
path = path.slice(1)
if (path.startsWith('/')) path = path.slice(1)
return await this.axios({
method: 'delete',
url: `/v1/${kvName}/metadata/${path}`,
Expand Down Expand Up @@ -73,7 +76,8 @@ export class VaultApi extends PluginApi {
},
},
})
} else { // means 200 status
} else {
// means 200 status
const configUpdate = generateKVConfigUpdate(kvRes.data)
if (configUpdate) {
await this.axios({
Expand Down Expand Up @@ -124,7 +128,10 @@ export class VaultApi extends PluginApi {

Role = {
upsert: async (roleName: string, policies: string[]) => {
const appRoleEnabled = await isAppRoleEnabled(this.axios, await this.getToken())
const appRoleEnabled = await isAppRoleEnabled(
this.axios,
await this.getToken(),
)
if (!appRoleEnabled) return

await this.axios({
Expand All @@ -143,12 +150,9 @@ export class VaultApi extends PluginApi {
})
},
delete: async (roleName: string) => {
await this.axios.delete(
`/v1/auth/approle/role/${roleName}`,
{
headers: { 'X-Vault-Token': await this.getToken() },
},
)
await this.axios.delete(`/v1/auth/approle/role/${roleName}`, {
headers: { 'X-Vault-Token': await this.getToken() },
})
},
getCredentials: async (roleName: string) => {
const { data: dataRole } = await this.axios.get(
Expand Down Expand Up @@ -205,8 +209,7 @@ export class VaultProjectApi extends VaultApi {
}

public async list(path: string = '/'): Promise<string[]> {
if (!path.startsWith('/'))
path = `/${path}`
if (!path.startsWith('/')) path = `/${path}`

const listSecretPath: string[] = []
const response = await this.axios({
Expand All @@ -221,7 +224,9 @@ export class VaultProjectApi extends VaultApi {
if (response.status === 404) return listSecretPath
for (const key of response.data.data.keys) {
if (key.endsWith('/')) {
const subSecrets = await this.list(`${path.substring(this.basePath.length)}/${key}`)
const subSecrets = await this.list(
`${path.substring(this.basePath.length)}/${key}`,
)
subSecrets.forEach((secret) => {
listSecretPath.push(`${key}${secret}`)
})
Expand All @@ -232,22 +237,24 @@ export class VaultProjectApi extends VaultApi {
return listSecretPath.flat()
}

public async read(path: string = '/', options: ReadOptions = { throwIfNoEntry: true }) {
if (path.startsWith('/'))
path = path.slice(1)
public async read(
path: string = '/',
options: ReadOptions = { throwIfNoEntry: true },
) {
if (path.startsWith('/')) path = path.slice(1)
const response = await this.axios.get(
`/v1/${this.coreKvName}/data/${this.projectRootDir}/${this.basePath}/${path}`,
{
headers: { 'X-Vault-Token': await this.getToken() },
validateStatus: status => (options.throwIfNoEntry ? [200] : [200, 404]).includes(status),
validateStatus: status =>
(options.throwIfNoEntry ? [200] : [200, 404]).includes(status),
},
)
return response.data.data
}

public async write(body: object, path: string = '/') {
if (path.startsWith('/'))
path = path.slice(1)
if (path.startsWith('/')) path = path.slice(1)
const response = await this.axios.post(
`/v1/${this.coreKvName}/data/${this.projectRootDir}/${this.basePath}/${path}`,
{
Expand All @@ -259,9 +266,11 @@ export class VaultProjectApi extends VaultApi {
}

public async destroy(path: string = '/') {
if (path.startsWith('/'))
path = path.slice(1)
return super.destroy(`${this.projectRootDir}/${this.basePath}/${path}`, this.coreKvName)
if (path.startsWith('/')) path = path.slice(1)
return super.destroy(
`${this.projectRootDir}/${this.basePath}/${path}`,
this.coreKvName,
)
}

Project = {
Expand All @@ -276,7 +285,10 @@ export class VaultProjectApi extends VaultApi {
`path "${this.coreKvName}/data/${this.projectRootDir}/${this.basePath}/REGISTRY/ro-robot" { capabilities = ["read"] }`,
)
await this.Group.upsert()
await this.Role.upsert(this.roleName, [this.policyName.techRO, this.policyName.appFull])
await this.Role.upsert(this.roleName, [
this.policyName.techRO,
this.policyName.appFull,
])
},
delete: async () => {
await this.Kv.delete(this.projectKvName)
Expand All @@ -286,7 +298,10 @@ export class VaultProjectApi extends VaultApi {
await this.Role.delete(this.roleName)
},
getCredentials: async () => {
const appRoleEnabled = await isAppRoleEnabled(this.axios, await this.getToken())
const appRoleEnabled = await isAppRoleEnabled(
this.axios,
await this.getToken(),
)
if (!appRoleEnabled) return this.defaultAppRoleCredentials
const creds = await this.Role.getCredentials(this.roleName)
return {
Expand Down Expand Up @@ -344,6 +359,23 @@ export class VaultProjectApi extends VaultApi {
}
}

interface VaultValuesWithoutCredentials {
/** Slash-separated directory (root node of all Gitlab projects) */
projectsRootDir: string
}
interface VaultCredentialsWithoutRole {
url: string
kvName: string
}
interface VaultCredentialsWithRole {
url: string
kvName: string
roleId: string
secretId: string
}
type VaultCredentials = VaultCredentialsWithRole | VaultCredentialsWithoutRole
type VaultValues = VaultCredentials & VaultValuesWithoutCredentials

export class VaultZoneApi extends VaultApi {
private readonly kvName: string
private readonly policyName: string
Expand All @@ -357,7 +389,10 @@ export class VaultZoneApi extends VaultApi {

public async upsert() {
await this.Kv.upsert(this.kvName)
await this.Policy.upsert(this.policyName, `path "${this.kvName}/*" { capabilities = ["read"] }`)
await this.Policy.upsert(
this.policyName,
`path "${this.kvName}/*" { capabilities = ["read"] }`,
)
await this.Role.upsert(this.roleName, [this.policyName])
}

Expand All @@ -368,37 +403,41 @@ export class VaultZoneApi extends VaultApi {
}

public async write(body: object, path: string = '/') {
if (path.startsWith('/'))
path = path.slice(1)
const response = await this.axios.post(
`/v1/${this.kvName}/data/${path}`,
{
headers: { 'X-Vault-Token': await this.getToken() },
data: body,
},
)
if (path.startsWith('/')) path = path.slice(1)
const response = await this.axios.post(`/v1/${this.kvName}/data/${path}`, {
headers: { 'X-Vault-Token': await this.getToken() },
data: body,
})
return await response.data
}

public async destroy(path: string = '/') {
if (path.startsWith('/'))
path = path.slice(1)
if (path.startsWith('/')) path = path.slice(1)
return super.destroy(path, this.kvName)
}

public async getCredentials() {
const appRoleEnabled = await isAppRoleEnabled(this.axios, await this.getToken())
if (!appRoleEnabled) {
public async getCredentials(): Promise<VaultCredentials> {
const appRoleEnabled = await isAppRoleEnabled(
this.axios,
await this.getToken(),
)
if (appRoleEnabled) {
return {
url: getConfig().publicUrl,
kvName: this.kvName,
}
...(await this.Role.getCredentials(this.roleName)),
} as VaultCredentialsWithRole
}
const creds = await this.Role.getCredentials(this.roleName)
return {
url: getConfig().publicUrl,
kvName: this.kvName,
...creds,
} as VaultCredentialsWithoutRole
}

public async getValues(): Promise<VaultValues> {
return {
projectsRootDir: getConfig().projectsRootDir,
...(await this.getCredentials()),
}
}
}
Loading