Skip to content

Commit

Permalink
feat: ✨ build project kv with config for projects
Browse files Browse the repository at this point in the history
  • Loading branch information
this-is-tobi committed Oct 3, 2024
1 parent 59bf2d5 commit 4ba6245
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 18 deletions.
98 changes: 85 additions & 13 deletions plugins/vault/src/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ interface readOptions {
}
interface AppRoleCredentials {
url: string
kvName: string
coreKvName: string
roleId: string
secretId: string
}

export class VaultProjectApi extends PluginApi {
private token: string | undefined = undefined
private axios: AxiosInstance
private kvName: string = 'forge-dso'
private coreKvName: string = 'forge-dso'
private projectKvName: string
private groupName: string
private policyName: string
private basePath: string
private roleName: string
private projectRootDir: string | undefined
Expand All @@ -27,6 +30,9 @@ export class VaultProjectApi extends PluginApi {
super()
this.basePath = `${project.organization.name}/${project.name}`
this.roleName = `${project.organization.name}-${project.name}`
this.projectKvName = `${project.organization.name}-${project.name}`
this.groupName = `${project.organization.name}-${project.name}`
this.policyName = `${project.organization.name}-${project.name}`
this.projectRootDir = removeTrailingSlash(requiredEnv('PROJECTS_ROOT_DIR'))
this.axios = axios.create({
baseURL: requiredEnv('VAULT_URL'),
Expand All @@ -36,7 +42,7 @@ export class VaultProjectApi extends PluginApi {
})
this.defaultAppRoleCredentials = {
url: removeTrailingSlash(requiredEnv('VAULT_URL')),
kvName: this.kvName,
coreKvName: this.coreKvName,
roleId: 'none',
secretId: 'none',
}
Expand All @@ -54,7 +60,7 @@ export class VaultProjectApi extends PluginApi {

const listSecretPath: string[] = []
const response = await this.axios({
url: `/v1/${this.kvName}/metadata/${this.projectRootDir}/${this.basePath}${path}/`,
url: `/v1/${this.coreKvName}/metadata/${this.projectRootDir}/${this.basePath}${path}/`,
headers: {
'X-Vault-Token': await this.getToken(),
},
Expand All @@ -80,7 +86,7 @@ export class VaultProjectApi extends PluginApi {
if (path.startsWith('/'))
path = path.slice(1)
const response = await this.axios.get(
`/v1/${this.kvName}/data/${this.projectRootDir}/${this.basePath}/${path}`,
`/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),
Expand All @@ -93,7 +99,7 @@ export class VaultProjectApi extends PluginApi {
if (path.startsWith('/'))
path = path.slice(1)
const response = await this.axios.post(
`/v1/${this.kvName}/data/${this.projectRootDir}/${this.basePath}/${path}`,
`/v1/${this.coreKvName}/data/${this.projectRootDir}/${this.basePath}/${path}`,
{
headers: { 'X-Vault-Token': await this.getToken() },
data: body,
Expand All @@ -102,10 +108,10 @@ export class VaultProjectApi extends PluginApi {
return await response.data
}

async upsertPolicy() {
async upsertPolicy(policyName: string, policy: string) {
await this.axios.put(
`/v1/sys/policies/acl/${this.roleName}`,
{ policy: `path "${this.kvName}/data/${this.projectRootDir}/${this.basePath}/*" { capabilities = ["create", "read", "update", "delete", "list"] }` },
`/v1/sys/policies/acl/${policyName}`,
{ policy },
{
headers: {
'X-Vault-Token': await this.getToken(),
Expand All @@ -115,20 +121,28 @@ export class VaultProjectApi extends PluginApi {
)
}

async isAppRoleEnabled() {
async getAuthMethod() {
const response = await this.axios.get(
'/v1/sys/auth',
{
headers: { 'X-Vault-Token': await this.getToken() },
},
)
return Object.keys(response.data).includes('approle/')
return response.data
}

async isAppRoleEnabled() {
const methods = await this.getAuthMethod()
return Object.keys(methods).includes('approle/')
}

public async upsertRole() {
const appRoleEnabled = await this.isAppRoleEnabled()
if (!appRoleEnabled) return
this.upsertPolicy()
this.upsertPolicy(
this.roleName,
`path "${this.coreKvName}/data/${this.projectRootDir}/${this.basePath}/*" { capabilities = ["create", "read", "update", "delete", "list"] }`,
)
this.axios.post(
`/v1/auth/approle/role/${this.roleName}`,
{
Expand Down Expand Up @@ -173,7 +187,7 @@ export class VaultProjectApi extends PluginApi {
if (path.startsWith('/'))
path = path.slice(1)
return this.axios.delete(
`/v1/${this.kvName}/metadata/${this.projectRootDir}/${this.basePath}/${path}`,
`/v1/${this.coreKvName}/metadata/${this.projectRootDir}/${this.basePath}/${path}`,
{
headers: { 'X-Vault-Token': await this.getToken() },
},
Expand All @@ -194,4 +208,62 @@ export class VaultProjectApi extends PluginApi {
},
)
}

public async upsertKv() {
const appRoleEnabled = await this.isAppRoleEnabled()
if (!appRoleEnabled) return
this.createKv(this.projectKvName)
this.upsertPolicy(
this.policyName,
`path "${this.projectKvName}/*" { capabilities = ["create", "read", "update", "delete", "list"] }\n\npath "/v1/sys/policies/acl/${this.policyName}/*" { capabilities = ["create", "read", "update", "list"] }`,
)
const group = await this.createGroup(this.groupName, this.policyName)
this.createGroupAlias(this.groupName, group.id)
}

public async createKv(kvName: string) {
await this.axios.post(
`/v1/sys/mounts/${kvName}`,
{
headers: { 'X-Vault-Token': await this.getToken() },
data: {
type: 'kv',
options: {
version: 2,
},
},
},
)
}

public async createGroup(group: string, policyName: string) {
const response = await this.axios.post(
`/v1/identity/group/${group}`,
{
headers: { 'X-Vault-Token': await this.getToken() },
data: {
name: group,
type: 'external',
policies: [policyName],
},
},
)
return response.data
}

public async createGroupAlias(groupAlias: string, groupId: string) {
const methods = await this.getAuthMethod()
const response = await this.axios.post(
`/v1/identity/group/${groupAlias}`,
{
headers: { 'X-Vault-Token': await this.getToken() },
data: {
name: groupAlias,
mount_accessor: methods['oidc/'].accessor,
canonical_id: groupId,
},
},
)
return response.data
}
}
2 changes: 1 addition & 1 deletion plugins/vault/src/functions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type Project, type StepCall, parseError } from '@cpn-console/hooks'

export const upsertProjectAppRole: StepCall<Project> = async (payload) => {
export const upsertProject: StepCall<Project> = async (payload) => {
try {
if (!payload.apis.vault) {
throw new Error('no Vault available')
Expand Down
4 changes: 2 additions & 2 deletions plugins/vault/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DefaultArgs, Plugin, Project, ProjectLite } from '@cpn-console/hooks'
import { archiveDsoProject, upsertProjectAppRole } from './functions.js'
import { archiveDsoProject, upsertProject } from './functions.js'
import infos from './infos.js'
import monitor from './monitor.js'
import { VaultProjectApi } from './class.js'
Expand All @@ -12,7 +12,7 @@ export const plugin: Plugin = {
getProjectSecrets: onlyApi,
upsertProject: {
...onlyApi,
steps: { main: upsertProjectAppRole },
steps: { main: upsertProject },
},
deleteProject: {
...onlyApi,
Expand Down
3 changes: 1 addition & 2 deletions plugins/vault/src/infos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ export const vaultUrl = process.env.VAULT_URL

const infos: ServiceInfos = {
name: 'vault',
// TODO wait for vault to be connected to oidc
// to: ({ project, organization }) => `${vaultUrl}/ui/vault/secrets/forge-dso/list/${projectRootDir}/${organization}/${project}`,
to: ({ project, organization }) => `${vaultUrl}/ui/vault/secrets/${organization}-${project}`,
title: 'Vault',
imgSrc: '/img/vault.svg',
description: 'Vault s\'intègre profondément avec les identités de confiance pour automatiser l\'accès aux secrets, aux données et aux systèmes',
Expand Down

0 comments on commit 4ba6245

Please sign in to comment.