feature: secret scanning architecture and github data source (wip)

This commit is contained in:
Scott Wilson
2025-05-28 22:21:03 -07:00
parent 931ee1e8da
commit e6c97510ca
231 changed files with 11443 additions and 1048 deletions
@@ -0,0 +1,3 @@
export enum GitHubRadarConnectionMethod {
App = "github-app"
}
@@ -0,0 +1,166 @@
import { createAppAuth } from "@octokit/auth-app";
import { Octokit } from "@octokit/rest";
import { AxiosResponse } from "axios";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { BadRequestError, ForbiddenRequestError, InternalServerError } from "@app/lib/errors";
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { AppConnection } from "../app-connection-enums";
import { GitHubRadarConnectionMethod } from "./github-radar-connection-enums";
import {
TGitHubRadarConnection,
TGitHubRadarConnectionConfig,
TGitHubRadarRepository
} from "./github-radar-connection-types";
export const getGitHubRadarConnectionListItem = () => {
const { INF_APP_CONNECTION_GITHUB_RADAR_APP_SLUG } = getConfig();
return {
name: "GitHub Radar" as const,
app: AppConnection.GitHubRadar as const,
methods: Object.values(GitHubRadarConnectionMethod) as [GitHubRadarConnectionMethod.App],
appClientSlug: INF_APP_CONNECTION_GITHUB_RADAR_APP_SLUG
};
};
export const getGitHubRadarClient = (appConnection: TGitHubRadarConnection) => {
const appCfg = getConfig();
const { method, credentials } = appConnection;
let client: Octokit;
switch (method) {
case GitHubRadarConnectionMethod.App:
if (!appCfg.INF_APP_CONNECTION_GITHUB_RADAR_APP_ID || !appCfg.INF_APP_CONNECTION_GITHUB_RADAR_APP_PRIVATE_KEY) {
throw new InternalServerError({
message: `GitHub ${getAppConnectionMethodName(method).replace(
"GitHub",
""
)} environment variables have not been configured`
});
}
client = new Octokit({
authStrategy: createAppAuth,
auth: {
appId: appCfg.INF_APP_CONNECTION_GITHUB_RADAR_APP_ID,
privateKey: appCfg.INF_APP_CONNECTION_GITHUB_RADAR_APP_PRIVATE_KEY,
installationId: credentials.installationId
}
});
break;
default:
throw new InternalServerError({
message: `Unhandled GitHub Radar connection method: ${method as GitHubRadarConnectionMethod}`
});
}
return client;
};
export const listGitHubRadarRepositories = async (appConnection: TGitHubRadarConnection) => {
const client = getGitHubRadarClient(appConnection);
const repositories: TGitHubRadarRepository[] = await client.paginate("GET /installation/repositories");
return repositories;
};
type TokenRespData = {
access_token: string;
scope: string;
token_type: string;
error?: string;
};
export const validateGitHubRadarConnectionCredentials = async (config: TGitHubRadarConnectionConfig) => {
const { credentials, method } = config;
const { INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_ID, INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_SECRET, SITE_URL } =
getConfig();
if (!INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_ID || !INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_SECRET) {
throw new InternalServerError({
message: `GitHub ${getAppConnectionMethodName(method).replace(
"GitHub",
""
)} environment variables have not been configured`
});
}
let tokenResp: AxiosResponse<TokenRespData>;
try {
tokenResp = await request.get<TokenRespData>("https://github.com/login/oauth/access_token", {
params: {
client_id: INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_ID,
client_secret: INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_SECRET,
code: credentials.code,
redirect_uri: `${SITE_URL}/organization/app-connections/github-radar/oauth/callback`
},
headers: {
Accept: "application/json",
"Accept-Encoding": "application/json"
}
});
} catch (e: unknown) {
throw new BadRequestError({
message: `Unable to validate connection: verify credentials`
});
}
if (tokenResp.status !== 200) {
throw new BadRequestError({
message: `Unable to validate credentials: GitHub responded with a status code of ${tokenResp.status} (${tokenResp.statusText}). Verify credentials and try again.`
});
}
if (method === GitHubRadarConnectionMethod.App) {
const installationsResp = await request.get<{
installations: {
id: number;
account: {
login: string;
type: string;
id: number;
};
}[];
}>(IntegrationUrls.GITHUB_USER_INSTALLATIONS, {
headers: {
Accept: "application/json",
Authorization: `Bearer ${tokenResp.data.access_token}`,
"Accept-Encoding": "application/json"
}
});
const matchingInstallation = installationsResp.data.installations.find(
(installation) => installation.id === +credentials.installationId
);
if (!matchingInstallation) {
throw new ForbiddenRequestError({
message: "User does not have access to the provided installation"
});
}
}
if (!tokenResp.data.access_token) {
throw new InternalServerError({ message: `Missing access token: ${tokenResp.data.error}` });
}
switch (method) {
case GitHubRadarConnectionMethod.App:
return {
installationId: credentials.installationId
};
default:
throw new InternalServerError({
message: `Unhandled GitHub connection method: ${method as GitHubRadarConnectionMethod}`
});
}
};
@@ -0,0 +1,66 @@
import { z } from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { GitHubRadarConnectionMethod } from "./github-radar-connection-enums";
export const GitHubRadarConnectionInputCredentialsSchema = z.object({
code: z.string().trim().min(1, "GitHub Radar App code required"),
installationId: z.string().min(1, "GitHub Radar App Installation ID required")
});
export const GitHubRadarConnectionOutputCredentialsSchema = z.object({
installationId: z.string()
});
export const ValidateGitHubRadarConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z
.literal(GitHubRadarConnectionMethod.App)
.describe(AppConnections.CREATE(AppConnection.GitHubRadar).method),
credentials: GitHubRadarConnectionInputCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.GitHubRadar).credentials
)
})
]);
export const CreateGitHubRadarConnectionSchema = ValidateGitHubRadarConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.GitHubRadar)
);
export const UpdateGitHubRadarConnectionSchema = z
.object({
credentials: GitHubRadarConnectionInputCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.GitHubRadar).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.GitHubRadar));
const BaseGitHubRadarConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.GitHubRadar) });
export const GitHubRadarConnectionSchema = BaseGitHubRadarConnectionSchema.extend({
method: z.literal(GitHubRadarConnectionMethod.App),
credentials: GitHubRadarConnectionOutputCredentialsSchema
});
export const SanitizedGitHubRadarConnectionSchema = z.discriminatedUnion("method", [
BaseGitHubRadarConnectionSchema.extend({
method: z.literal(GitHubRadarConnectionMethod.App),
credentials: GitHubRadarConnectionOutputCredentialsSchema.pick({})
})
]);
export const GitHubRadarConnectionListItemSchema = z.object({
name: z.literal("GitHub Radar"),
app: z.literal(AppConnection.GitHubRadar),
// the below is preferable but currently breaks with our zod to json schema parser
// methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]),
methods: z.nativeEnum(GitHubRadarConnectionMethod).array(),
appClientSlug: z.string().optional()
});
@@ -0,0 +1,24 @@
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { listGitHubRadarRepositories } from "@app/services/app-connection/github-radar/github-radar-connection-fns";
import { TGitHubRadarConnection } from "@app/services/app-connection/github-radar/github-radar-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TGitHubRadarConnection>;
export const githubRadarConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listRepositories = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.GitHubRadar, connectionId, actor);
const repositories = await listGitHubRadarRepositories(appConnection);
return repositories.map((repo) => ({ id: repo.id, name: repo.full_name }));
};
return {
listRepositories
};
};
@@ -0,0 +1,28 @@
import { z } from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateGitHubRadarConnectionSchema,
GitHubRadarConnectionSchema,
ValidateGitHubRadarConnectionCredentialsSchema
} from "./github-radar-connection-schemas";
export type TGitHubRadarConnection = z.infer<typeof GitHubRadarConnectionSchema>;
export type TGitHubRadarConnectionInput = z.infer<typeof CreateGitHubRadarConnectionSchema> & {
app: AppConnection.GitHubRadar;
};
export type TValidateGitHubRadarConnectionCredentialsSchema = typeof ValidateGitHubRadarConnectionCredentialsSchema;
export type TGitHubRadarConnectionConfig = DiscriminativePick<
TGitHubRadarConnectionInput,
"method" | "app" | "credentials"
>;
export type TGitHubRadarRepository = {
id: number;
full_name: string;
};
@@ -0,0 +1,4 @@
export * from "./github-radar-connection-enums";
export * from "./github-radar-connection-fns";
export * from "./github-radar-connection-schemas";
export * from "./github-radar-connection-types";