Skip to content

Commit

Permalink
infra: sandbox environment (#1441)
Browse files Browse the repository at this point in the history
* fix: lib returns empty string

* infra: set up secrets for sandbox env

* fix: duplicate SecurityGroupRule

it seems awsx creates a rule already, which was conflictin with the explicitly set one.
testing on the sandbox environment shows that metabase is accessible this explicit
ingress rule but if production gets broken we know this might be the culprit

* fix: ignore team secrets on sandbox environment

* fix: api airbrake env

* refact: make all requireSecret
  • Loading branch information
gunar authored Feb 20, 2023
1 parent be796f1 commit e34a404
Show file tree
Hide file tree
Showing 11 changed files with 3,072 additions and 72 deletions.
14 changes: 7 additions & 7 deletions api.planx.uk/airbrake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { isLiveEnv } from "./helpers";

const airbrake =
isLiveEnv() &&
process.env.AIRBRAKE_PROJECT_ID &&
process.env.AIRBRAKE_PROJECT_KEY
process.env.AIRBRAKE_PROJECT_ID &&
process.env.AIRBRAKE_PROJECT_KEY
? new Notifier({
projectId: Number(process.env.AIRBRAKE_PROJECT_ID),
projectKey: process.env.AIRBRAKE_PROJECT_KEY,
environment: process.env.NODE_ENV!
})
projectId: Number(process.env.AIRBRAKE_PROJECT_ID),
projectKey: process.env.AIRBRAKE_PROJECT_KEY,
environment: process.env.NODE_ENV!,
})
: undefined;

export default airbrake;
export default airbrake;
73 changes: 49 additions & 24 deletions api.planx.uk/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { gql } from 'graphql-request';
import { capitalize } from 'lodash';
import { gql } from "graphql-request";
import { capitalize } from "lodash";
import { adminGraphQLClient as adminClient } from "./hasura";
import { Flow, Node } from "./types";

Expand All @@ -14,26 +14,40 @@ const getFlowData = async (id: string): Promise<Flow> => {
team_id
}
}
`,
`,
{ id }
);

return data.flows_by_pk;
};

// Insert a new flow into the `flows` table
const insertFlow = async (teamId: number, slug: string, flowData: Flow["data"], creatorId?: number, copiedFrom?: Flow["id"]) => {
const insertFlow = async (
teamId: number,
slug: string,
flowData: Flow["data"],
creatorId?: number,
copiedFrom?: Flow["id"]
) => {
const data = await adminClient.request(
gql`
mutation InsertFlow ($team_id: Int!, $slug: String!, $data: jsonb = {}, $creator_id: Int, $copied_from: uuid) {
insert_flows_one(object: {
team_id: $team_id,
slug: $slug,
data: $data,
version: 1,
creator_id: $creator_id
copied_from: $copied_from
}) {
mutation InsertFlow(
$team_id: Int!
$slug: String!
$data: jsonb = {}
$creator_id: Int
$copied_from: uuid
) {
insert_flows_one(
object: {
team_id: $team_id
slug: $slug
data: $data
version: 1
creator_id: $creator_id
copied_from: $copied_from
}
) {
id
}
}
Expand All @@ -55,8 +69,10 @@ const insertFlow = async (teamId: number, slug: string, flowData: Flow["data"],
const createAssociatedOperation = async (flowId: Flow["id"]) => {
const data = await adminClient.request(
gql`
mutation InsertOperation ($flow_id: uuid!, $data: jsonb = {}) {
insert_operations_one(object: { flow_id: $flow_id, version: 1, data: $data }) {
mutation InsertOperation($flow_id: uuid!, $data: jsonb = {}) {
insert_operations_one(
object: { flow_id: $flow_id, version: 1, data: $data }
) {
id
}
}
Expand All @@ -70,7 +86,9 @@ const createAssociatedOperation = async (flowId: Flow["id"]) => {
};

// Get the most recent version of a published flow's data (flattened, with external portal nodes)
const getMostRecentPublishedFlow = async (id: string): Promise<Flow["data"]> => {
const getMostRecentPublishedFlow = async (
id: string
): Promise<Flow["data"]> => {
const data = await adminClient.request(
gql`
query GetMostRecentPublishedFlow($id: uuid!) {
Expand All @@ -95,9 +113,9 @@ const getPublishedFlowByDate = async (id: string, created_at: string) => {
query GetPublishedFlowByDate($id: uuid!, $created_at: timestamptz!) {
flows_by_pk(id: $id) {
published_flows(
limit: 1,
order_by: { created_at: desc },
where: { created_at: {_lte: $created_at} }
limit: 1
order_by: { created_at: desc }
where: { created_at: { _lte: $created_at } }
) {
data
}
Expand Down Expand Up @@ -164,7 +182,10 @@ const getChildren = (
/**
* For a given flow, make it unique by renaming its' node ids (replace last n characters) while preserving its' content
*/
const makeUniqueFlow = (flowData: Flow["data"], replaceValue: string): Flow["data"] => {
const makeUniqueFlow = (
flowData: Flow["data"],
replaceValue: string
): Flow["data"] => {
const charactersToReplace = replaceValue.length;

Object.keys(flowData).forEach((node) => {
Expand All @@ -188,17 +209,21 @@ const makeUniqueFlow = (flowData: Flow["data"], replaceValue: string): Flow["dat
return flowData;
};

const isLiveEnv = () => (["production", "staging", "pizza"].includes(process.env.NODE_ENV || ""));

const isLiveEnv = () =>
["production", "staging", "pizza", "sandbox"].includes(
process.env.NODE_ENV || ""
);

/**
* Get current environment, formatted for display
*/
const getFormattedEnvironment = (): string => {
let environment = process.env.NODE_ENV;
if (environment === "pizza") {
const pizzaNumber = new URL(process.env.API_URL_EXT!).href.split(".")[1]
environment += ` ${pizzaNumber}`
};
const pizzaNumber = new URL(process.env.API_URL_EXT!).href.split(".")[1];
environment += ` ${pizzaNumber}`;
}
return capitalize(environment);
};

Expand Down
36 changes: 36 additions & 0 deletions infrastructure/application/Pulumi.sandbox.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
config:
application:airbrake-project-id: "329753"
application:airbrake-project-key:
secure: AAABAO8NgmVNdkwajWpiKwi+dxGicFACbAQzK66FREUCp+OUbSRyNAb9dSQHq/n9fZb8xWBdvYBCQ9eBr2jqPQ==
application:bops-api-root-domain: bops-staging.services
application:bops-api-token:
secure: AAABAHLQf7CFOK073geL2TbTYycQCzPYUy3Xi66UniwIrTlOAqve2YBikJXzMJ2xS9v4dGUZMUmwUba0OpTz8KAMXJ8=
application:cloudflare-zone-id: 9cdfc35484748e96b0ed2b1d303a694f
application:file-api-key:
secure: AAABAK75LzZkxv9S1kx8w+DR9aVnIS/64z5SiPUdyC8XvNEhBCuSwmYlo6Q+dyay8wtMk+SQzHspppLfiI5BcK/4YYNPZTijIrgIwA==
application:google-client-id: 987324067365-vpsk3kgeq5n32ihjn760ihf8l7m5rhh8.apps.googleusercontent.com
application:google-client-secret:
secure: AAABAAYYW3KQvsCuEPpxIWCB8Nrs6kfYIFcgxCoBknmYs9s2TOifXvhbW2YEzp8rNBvwmOz78sm49i0W5LxjpdZAkQ==
application:govuk-notify-api-key:
secure: AAABAN6by1UpKop5de/dlYA003xrp1LMzLG60J2asKxioUMoUS0SsP7N+1KT53aKnux45aUZ6J+ryYpGge4+FkDhEZEC85Sw0+q/bMzKy1VER9BFIU6Qzxr/shdSVwzOW1ZZQNc/Q1cZjfY4umGbEiBipz1f
application:hasura-admin-secret:
secure: AAABADc3VLm2XyLMC0Ap/5n9dk7qZnu2QdE4UYLHWbjtukjefJxLD85nQpPftKH7yPWF8FyS4wETj3xoLux+EVcRkwkP8674E/3shA==
application:hasura-planx-api-key:
secure: AAABAKVQcDltAw2qwW7xlZJ22Squvgir7cRker6goWiV194dWlaGtmwOPjC0HZXexoYfm8O4pFSSRCLQZhan/Ta1/vY=
application:jwt-secret:
secure: AAABAOIyX8iQxrly6pxlhc5k05qHmz2HvQS6Pi0HEGDGyT3F4hnat6SUc9AF4Q0hszW+/+qn7tZ3Vnavq6/jBAAITVSQ+EDvXFMaMg==
application:metabase-encryption-secret-key:
secure: AAABADD9S3yimUNDyeHEz7tUW80VrQ3KCmeshMK+HvBg4j8uvwWvrQ48uGVjW+i0RZMmeAGdsqMtyBi4KQfBizNT0lJBrWS4SMf6VA==
application:metabasePgPassword:
secure: AAABAHGBg6KBmGvFkCFvTHV2ZZue7qABz36u1Uc/p07Te9F6CxAGyh6fqJGEg8XfqNlzHwxhPUjuEA+wd4EgOMRqMN1a93eBdkdBVw==
application:ordnance-survey-api-key:
secure: AAABAFOo/4XWkrVjKSBcYGxaJzwiAMAhEpR4HOetm9f6r6x2PBHsK4/8Tf9XYGACKtjbjpqmTk5S1Jd5Z5WdWQ==
application:session-secret:
secure: AAABANgFXfc2ZZ2PtBUIWsc9xEF0PdBXlCkjLrIpKgap9fMEPisJoUK6L8+qQtxTGNswGoHSsiYttCQU4JygWCSjLd/bB7FoBgI3lw==
application:slack-webhook-url:
secure: AAABAK+2TCk3O3T6fai/7ahl1a/QOs4P/bNnrLUV7VqewfAU6ZjYsgUIhJ3kgHaeHLndloMD6R3ytRI3DwCxyiL78UIiPOwmkxUBdZr0IOerOqPZzTwpILCHNUC4UOXCdg3Ipj6u3r7ntU5sPkGt
application:uniform-submission-url: https://staging.identity.idoxgroup.com/agw/submission-api
application:uniform-token-url:
secure: AAABAOJAT0wX/byM7wyaXGB+H4m0NnEmaJP8NiM2u+7orIYvIM5yQfDb1GEAgoyMWRSn+7KFDYnCIvf5BNTyIHt1Usbr3R0rClm+zFbEZCuPV8ef2I8=
cloudflare:apiToken:
secure: AAABAPDjpKRV8PLulg+5kQfu5roxHWLgDH/zGT2+J+SKprEZJaJyLxr+98Q6oNcoX9h+/9elmdK9S4dFeBpZ/zQeMmPTdd+h
52 changes: 22 additions & 30 deletions infrastructure/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const data = new pulumi.StackReference(`planx/data/${env}`);

// The @pulumi/cloudflare package doesn't generate errors so this is here just to create a warning in case the CloudFlare API token is missing.
// You can generate tokens here: https://dash.cloudflare.com/profile/api-tokens
new pulumi.Config("cloudflare").require("apiToken");
new pulumi.Config("cloudflare").requireSecret("apiToken");

const CUSTOM_DOMAINS =
env === "production"
Expand Down Expand Up @@ -71,7 +71,7 @@ export = async () => {
database: pgRoot.path!.substring(1) as string,
superuser: false,
});
const metabasePgPassword = config.require("metabasePgPassword");
const metabasePgPassword = config.requireSecret("metabasePgPassword");
const role = new postgres.Role(
"metabase",
{
Expand Down Expand Up @@ -100,14 +100,6 @@ export = async () => {
securityGroups: [
new awsx.ec2.SecurityGroup("metabase-custom-port", {
vpc,
ingress: [
{
protocol: "tcp",
cidrBlocks: ["0.0.0.0/0"],
fromPort: 443,
toPort: 443,
},
],
egress: [
{
protocol: "tcp",
Expand Down Expand Up @@ -182,7 +174,7 @@ export = async () => {
// https://www.metabase.com/docs/latest/operations-guide/encrypting-database-details-at-rest.html
{
name: "MB_ENCRYPTION_SECRET_KEY",
value: config.require("metabase-encryption-secret-key"),
value: config.requireSecret("metabase-encryption-secret-key"),
},
],
},
Expand Down Expand Up @@ -315,28 +307,28 @@ export = async () => {
{ name: "AWS_S3_ACL", value: "public-read" },
{
name: "FILE_API_KEY",
value: config.require("file-api-key"),
value: config.requireSecret("file-api-key"),
},
{
name: "GOOGLE_CLIENT_ID",
value: config.require("google-client-id"),
},
{
name: "GOOGLE_CLIENT_SECRET",
value: config.require("google-client-secret"),
value: config.requireSecret("google-client-secret"),
},
{ name: "SESSION_SECRET", value: config.require("session-secret") },
{ name: "SESSION_SECRET", value: config.requireSecret("session-secret") },
{ name: "API_URL_EXT", value: `https://api.${DOMAIN}` },
{
name: "BOPS_API_ROOT_DOMAIN",
value: config.require("bops-api-root-domain"),
value: config.requireSecret("bops-api-root-domain"),
},
{ name: "BOPS_API_TOKEN", value: config.require("bops-api-token") },
{ name: "JWT_SECRET", value: config.require("jwt-secret") },
{ name: "BOPS_API_TOKEN", value: config.requireSecret("bops-api-token") },
{ name: "JWT_SECRET", value: config.requireSecret("jwt-secret") },
{ name: "PORT", value: String(API_PORT) },
{
name: "HASURA_GRAPHQL_ADMIN_SECRET",
value: config.require("hasura-admin-secret"),
value: config.requireSecret("hasura-admin-secret"),
},
{
name: "HASURA_GRAPHQL_URL",
Expand All @@ -352,27 +344,27 @@ export = async () => {
},
{
name: "HASURA_PLANX_API_KEY",
value: config.require("hasura-planx-api-key"),
value: config.requireSecret("hasura-planx-api-key"),
},
{
name: "AIRBRAKE_PROJECT_ID",
value: config.require("airbrake-project-id"),
value: config.requireSecret("airbrake-project-id"),
},
{
name: "AIRBRAKE_PROJECT_KEY",
value: config.require("airbrake-project-key"),
value: config.requireSecret("airbrake-project-key"),
},
{
name: "UNIFORM_TOKEN_URL",
value: config.require("uniform-token-url"),
value: config.requireSecret("uniform-token-url"),
},
{
name: "UNIFORM_SUBMISSION_URL",
value: config.require("uniform-submission-url"),
value: config.requireSecret("uniform-submission-url"),
},
{
name: "GOVUK_NOTIFY_API_KEY",
value: config.require("govuk-notify-api-key"),
value: config.requireSecret("govuk-notify-api-key"),
},
{
name: "GOVUK_NOTIFY_SAVE_RETURN_EMAIL_TEMPLATE_ID",
Expand Down Expand Up @@ -400,13 +392,13 @@ export = async () => {
},
{
name: "SLACK_WEBHOOK_URL",
value: config.require("slack-webhook-url"),
value: config.requireSecret("slack-webhook-url"),
},
{
name: "ORDNANCE_SURVEY_API_KEY",
value: config.require("ordnance-survey-api-key"),
value: config.requireSecret("ordnance-survey-api-key"),
},
...generateTeamSecrets(config),
...generateTeamSecrets(config, env),
],
},
},
Expand All @@ -417,7 +409,7 @@ export = async () => {
? `api.${tldjs.getSubdomain(DOMAIN)}`
: "api",
type: "CNAME",
zoneId: config.require("cloudflare-zone-id"),
zoneId: config.requireSecret("cloudflare-zone-id"),
value: apiListenerHttps.endpoint.hostname,
ttl: 1,
proxied: false,
Expand Down Expand Up @@ -476,7 +468,7 @@ export = async () => {
{ name: "PORT", value: String(SHAREDB_PORT) },
{
name: "JWT_SECRET",
value: config.require("jwt-secret"),
value: config.requireSecret("jwt-secret"),
},
{
name: "PG_URL",
Expand Down Expand Up @@ -633,7 +625,7 @@ export = async () => {
const cdn = createCdn({ domain: DOMAIN, acmCertificateArn: sslCert.arn });

const frontendDnsRecord = new cloudflare.Record("frontend", {
name: tldjs.getSubdomain(DOMAIN) ?? "@",
name: tldjs.getSubdomain(DOMAIN) || "@",
type: "CNAME",
zoneId: config.require("cloudflare-zone-id"),
value: cdn.domainName,
Expand Down
Loading

0 comments on commit e34a404

Please sign in to comment.