Skip to content

Commit

Permalink
feat: Verify job ownership (#37)
Browse files Browse the repository at this point in the history
Co-authored-by: Lukas Jost <[email protected]>
  • Loading branch information
meyfa and lusu007 authored Jul 13, 2024
1 parent 90a2a02 commit f854f8c
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 4 deletions.
2 changes: 1 addition & 1 deletion backend/src/api/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { authenticateSession } from '../auth/common.js'
import { V1Job } from '@kubernetes/client-node'
import assert from 'node:assert'
import { DEFAULT_NAMESPACE } from '../kubernetes/api.js'
import { ForemanAnnotations } from '../annotations.js'
import { ForemanAnnotations } from '../metadata.js'

interface JobsItem {
namespace: string
Expand Down
18 changes: 16 additions & 2 deletions backend/src/controllers/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { KubernetesApi } from '../kubernetes/api.js'
import assert from 'node:assert'
import { StrongCache } from '../util/cache.js'
import { V1CronJob, V1Job } from '@kubernetes/client-node'
import { ForemanLabels } from '../metadata.js'

export class JobController {
private readonly cache = new StrongCache<V1Job[]>(5000)
Expand All @@ -23,9 +24,22 @@ export class JobController {
assert.ok(namespace != null)

return await this.cache.lazyCompute([namespace], async () => {
return await this.k8s.getJobs({
const jobs = await this.k8s.getJobs({
namespace
// TODO ensure owner
})
// Ensure the job is associated with the cronjob
return jobs?.filter((job) => {
// manually-triggered jobs
if (job.metadata?.labels?.[ForemanLabels.CronJob] === cronJob.metadata?.name) {
return true
}
// direct owner reference
return job.metadata?.ownerReferences?.some((ref) => {
return ref.controller === true &&
ref.apiVersion === cronJob.apiVersion &&
ref.kind === cronJob.kind &&
ref.name === cronJob.metadata?.name
}) === true
})
})
}
Expand Down
7 changes: 6 additions & 1 deletion backend/src/controllers/trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CronJobController } from './cronjob.js'
import assert from 'node:assert'
import { randomBytes } from 'node:crypto'
import { V1Job } from '@kubernetes/client-node'
import { ForemanAnnotations } from '../annotations.js'
import { ForemanAnnotations, ForemanLabels } from '../metadata.js'
import { JobController } from './job.js'

export class TriggerController {
Expand Down Expand Up @@ -40,10 +40,15 @@ export class TriggerController {
annotations[ForemanAnnotations.DebugLogging] = 'true'
}

// Ensure we can attribute the job to the cronjob later
const labels: Record<string, string> = {}
labels[ForemanLabels.CronJob] = cronJob.metadata.name

const result = await this.k8s.triggerCronJob({
cronJob,
jobName: `${cronJob.metadata.name}-foreman-${randomId()}`,
annotations,
labels,
env
})

Expand Down
15 changes: 15 additions & 0 deletions backend/src/kubernetes/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export class KubernetesApi {
cronJob: V1CronJob
jobName: string
annotations?: Record<string, string>
labels?: Record<string, string>
env: Record<string, string>
}): Promise<V1Job> {
assert.ok(options.cronJob.metadata?.namespace != null)
Expand All @@ -110,6 +111,7 @@ export class KubernetesApi {
let jobBody = options.cronJob.spec.jobTemplate
jobBody = applyMetadataName(jobBody, options.jobName)
jobBody = applyMetadataAnnotations(jobBody, options.annotations ?? {})
jobBody = applyMetadataLabels(jobBody, options.labels ?? {})
jobBody = applyEnvToJobContainers(jobBody, options.env)
// Cleanup job after 1 hour
jobBody = applyTtl(jobBody, 60 * 60)
Expand All @@ -133,6 +135,19 @@ function applyMetadataAnnotations (jobBody: k8s.V1Job, annotations: Record<strin
}
}

function applyMetadataLabels (jobBody: k8s.V1Job, labels: Record<string, string>): k8s.V1Job {
return {
...jobBody,
metadata: {
...jobBody.metadata,
labels: {
...jobBody.metadata?.labels,
...labels
}
}
}
}

function applyMetadataName (jobBody: k8s.V1Job, name: string): k8s.V1Job {
return {
...jobBody,
Expand Down
4 changes: 4 additions & 0 deletions backend/src/annotations.ts → backend/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ export enum ForemanAnnotations {
RepositoryScope = 'foreman.contane.net/repository-scope',
DebugLogging = 'foreman.contane.net/debug-logging'
}

export enum ForemanLabels {
CronJob = 'foreman.contane.net/cronjob'
}

0 comments on commit f854f8c

Please sign in to comment.