Skip to content

Commit

Permalink
[ENG-1229] create github API key system to authenticate konfig pr-mer…
Browse files Browse the repository at this point in the history
…ge and pr-create (#225)
  • Loading branch information
eddiechayes authored Sep 29, 2023
1 parent a4cdd85 commit fc8a924
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 2 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/guard-api-key-leak.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Guard API Key Leak
on: push

jobs:
detect-key-leak:
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v3

- name: yarn build
run: yarn && yarn build
working-directory: ./generator/konfig-dash
env:
# required to avoid `Usage Error: Environment variable not found (NPM_TOKEN)`
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Detect API key leaks
run: ./generator/konfig-dash/bash-scripts/guard-api-key-leak.sh
6 changes: 6 additions & 0 deletions generator/konfig-dash/.changeset/thick-geese-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'konfig-openapi-spec': patch
'konfig-cli': patch
---

add api keys for /prCreate and /prMerge endpoints
7 changes: 7 additions & 0 deletions generator/konfig-dash/api/src/functions/prCreate/prCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { App } from 'octokit'

import { findRepository } from 'konfig-lib'
import { CORS_HEADERS_ORIGIN } from 'src/lib/cors-headers'
import { validateApiKey } from 'src/lib/api-keys'
import { API_KEY_HEADER_NAME } from 'konfig-lib'
import {
PrCreateResponseBodyType,
PrCreateResponseBody,
Expand Down Expand Up @@ -35,6 +37,11 @@ export const handler = async (event: APIGatewayEvent, context: Context) => {
if (privateKey === undefined)
throw Error('Missing GITHUB_APP_PRIVATE_KEY Environment Variable')

validateApiKey({
key: event.headers[API_KEY_HEADER_NAME],
owner: requestBodyParseResult.data.owner,
})

const { eachRepository } = new App({
appId: 276014,
privateKey,
Expand Down
7 changes: 7 additions & 0 deletions generator/konfig-dash/api/src/functions/prMerge/prMerge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { logger } from 'src/lib/logger'
import { App } from 'octokit'

import { findRepository } from 'konfig-lib'
import { validateApiKey } from 'src/lib/api-keys'
import { API_KEY_HEADER_NAME } from 'konfig-lib'
import { CORS_HEADERS_ORIGIN } from 'src/lib/cors-headers'
import {
PrMergeResponseBodyType,
Expand Down Expand Up @@ -35,6 +37,11 @@ export const handler = async (event: APIGatewayEvent, context: Context) => {
if (privateKey === undefined)
throw Error('Missing GITHUB_APP_PRIVATE_KEY Environment Variable')

validateApiKey({
key: event.headers[API_KEY_HEADER_NAME],
owner: requestBodyParseResult.data.owner,
})

const { eachRepository } = new App({
appId: 276014,
privateKey,
Expand Down
18 changes: 18 additions & 0 deletions generator/konfig-dash/api/src/lib/api-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { API_KEY_HEADER_NAME } from 'konfig-lib'

const apiKeys: Record<string, string> = {
qXx6mYhoJgbj8brJe11NeBNsul375Nv3: 'humanloop',
Mu82mVyHm4D1p1zljd8T8sQvxtaIZ5b2: 'konfig-dev',
}

export function validateApiKey({
key,
owner,
}: {
key: string | undefined
owner: string
}) {
if (key === undefined) throw Error(`Missing header ${API_KEY_HEADER_NAME}`)
if (apiKeys[key] === undefined || apiKeys[key] !== owner)
throw Error(`Invalid header ${API_KEY_HEADER_NAME}`)
}
1 change: 1 addition & 0 deletions generator/konfig-dash/bash-scripts/build.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/bin/bash

yarn workspaces foreach -pitv -j unlimited run build
yarn rw build api
34 changes: 34 additions & 0 deletions generator/konfig-dash/bash-scripts/guard-api-key-leak.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash

cd "$(dirname "$0")"

# get the api keys from api-keys.ts
keyFile="../api/src/lib/api-keys.ts"
apiKeys=($(grep -oE '[A-Za-z0-9]{32}:' "$keyFile" | sed 's/://'))

searchFolder="../../.." # entire repo
foundKey=false

# Files that are allowed to contain the api key
whitelist=(
"../../../generator/konfig-dash/api/dist/lib/api-keys.js"
"../../../generator/konfig-dash/.redwood/prebuild/api/src/lib/api-keys.js"
"../../../generator/konfig-dash/api/src/lib/api-keys.ts"
"../../../generator/konfig-dash/api/dist/lib/api-keys.js.map"
)

for apiKey in "${apiKeys[@]}"; do
files=$(grep -rl "$apiKey" "$searchFolder" | grep -vE "$(printf "%s\n" "${whitelist[@]}")")
if [ -n "$files" ]; then
foundKey=true
echo "ERROR: Security risk detected. API key leaked in the following files:"
echo "$files"
echo
fi
done

if [ "$foundKey" = true ]; then
exit 1
fi
echo No api key leaks detected.
exit 0
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
PrCreateRequestBodyType,
PrCreateResponseBody,
} from 'konfig-openapi-spec'
import { API_KEY_HEADER_NAME } from 'konfig-lib'

export default class PrCreate extends Command {
static description = 'Creates a github pull request'
Expand Down Expand Up @@ -48,6 +49,10 @@ export default class PrCreate extends Command {
public async run(): Promise<void> {
const { flags } = await this.parse(PrCreate)

const apiKey = process.env.KONFIG_API_KEY
if (apiKey === undefined)
throw Error('Missing KONFIG_API_KEY Environment Variable')

const url = flags.dev
? 'http://localhost:8911/prCreate'
: 'https://api.konfigthis.com/prCreate'
Expand All @@ -67,7 +72,9 @@ export default class PrCreate extends Command {
const suffix = `PR in ${flags.owner}/${flags.repo} from ${flags.head} to ${baseStr}`

CliUx.ux.action.start(`Creating ${suffix}`)
const result = await axios.post(url, body)
const result = await axios.post(url, body, {
headers: { [API_KEY_HEADER_NAME]: apiKey },
})
CliUx.ux.action.stop()

if (result.status === 200) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
PrMergeRequestBodyType,
PrMergeResponseBody,
} from 'konfig-openapi-spec'
import { API_KEY_HEADER_NAME } from 'konfig-lib'

export default class PrMerge extends Command {
static description = 'Merges a github pull request'
Expand Down Expand Up @@ -40,6 +41,10 @@ export default class PrMerge extends Command {
public async run(): Promise<void> {
const { flags } = await this.parse(PrMerge)

const apiKey = process.env.KONFIG_API_KEY
if (apiKey === undefined)
throw Error('Missing KONFIG_API_KEY Environment Variable')

const url = flags.dev
? 'http://localhost:8911/prMerge'
: 'https://api.konfigthis.com/prMerge'
Expand All @@ -57,7 +62,9 @@ export default class PrMerge extends Command {
const suffix = `PR from ${flags.head} to ${baseStr} in repository ${flags.owner}/${flags.repo}`

CliUx.ux.action.start(`Merging ${suffix}`)
const result = await axios.post(url, body)
const result = await axios.post(url, body, {
headers: { [API_KEY_HEADER_NAME]: apiKey },
})
CliUx.ux.action.stop()

if (result.status === 200) {
Expand Down
1 change: 1 addition & 0 deletions generator/konfig-dash/packages/konfig-lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ export * from './forEachReferenceUntyped'
export * from './recurseObject'
export * from './snaptrade-demo'
export * from './util/find-repository'
export * from './util/konfig-api-key-header'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const API_KEY_HEADER_NAME = 'x-konfig-api-key'

0 comments on commit fc8a924

Please sign in to comment.