Skip to content

Commit

Permalink
chore(env): add back env
Browse files Browse the repository at this point in the history
  • Loading branch information
kishore03109 committed Jun 20, 2024
1 parent 60a4288 commit b5bbe94
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 22 deletions.
6 changes: 6 additions & 0 deletions .aws/deploy/backend-task-definition.prod.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
"valueFrom": "PROD_INCOMING_QUEUE_URL"
},
{ "name": "JWT_SECRET", "valueFrom": "PROD_JWT_SECRET" },
{ "name": "KEYCDN_API_KEY", "valueFrom": "PROD_KEYCDN_API_KEY" },
{
"name": "MAX_NUM_OTP_ATTEMPTS",
"valueFrom": "PROD_MAX_NUM_OTP_ATTEMPTS"
Expand Down Expand Up @@ -132,6 +133,11 @@
"name": "REDIRECT_URI",
"valueFrom": "PROD_REDIRECT_URI"
},
{
"name": "REDIRECTION_REPO_GITHUB_TOKEN",
"valueFrom": "PROD_REDIRECTION_REPO_GITHUB_TOKEN"
},
{ "name": "REDIS_HOST", "valueFrom": "PROD_REDIS_HOST" },
{
"name": "SESSION_SECRET",
"valueFrom": "PROD_SESSION_SECRET"
Expand Down
6 changes: 6 additions & 0 deletions .aws/deploy/backend-task-definition.staging.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"valueFrom": "STAGING_INCOMING_QUEUE_URL"
},
{ "name": "JWT_SECRET", "valueFrom": "STAGING_JWT_SECRET" },
{ "name": "KEYCDN_API_KEY", "valueFrom": "STAGING_KEYCDN_API_KEY" },
{
"name": "MAX_NUM_OTP_ATTEMPTS",
"valueFrom": "STAGING_MAX_NUM_OTP_ATTEMPTS"
Expand Down Expand Up @@ -141,6 +142,11 @@
"name": "REDIRECT_URI",
"valueFrom": "STAGING_REDIRECT_URI"
},
{
"name": "REDIRECTION_REPO_GITHUB_TOKEN",
"valueFrom": "STAGING_REDIRECTION_REPO_GITHUB_TOKEN"
},
{ "name": "REDIS_HOST", "valueFrom": "STAGING_REDIS_HOST" },
{
"name": "SESSION_SECRET",
"valueFrom": "STAGING_SESSION_SECRET"
Expand Down
4 changes: 4 additions & 0 deletions .aws/deploy/support-task-definition.prod.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
"name": "REDIRECT_URI",
"valueFrom": "PROD_REDIRECT_URI"
},
{
"name": "REDIRECTION_REPO_GITHUB_TOKEN",
"valueFrom": "PROD_REDIRECTION_REPO_GITHUB_TOKEN"
},
{ "name": "REDIS_HOST", "valueFrom": "PROD_REDIS_HOST" },
{
"name": "SESSION_SECRET",
Expand Down
4 changes: 4 additions & 0 deletions .aws/deploy/support-task-definition.staging.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@
"name": "REDIRECT_URI",
"valueFrom": "STAGING_REDIRECT_URI"
},
{
"name": "REDIRECTION_REPO_GITHUB_TOKEN",
"valueFrom": "STAGING_REDIRECTION_REPO_GITHUB_TOKEN"
},
{ "name": "REDIS_HOST", "valueFrom": "STAGING_REDIS_HOST" },
{
"name": "SESSION_SECRET",
Expand Down
7 changes: 7 additions & 0 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,13 @@ const config = convict({
format: "required-string",
default: "",
},
redirectionRepoGithubToken: {
doc: "Github access to read opengovsg/isomer-redirection",
env: "REDIRECTION_REPO_GITHUB_TOKEN",
sensitive: true,
format: "required-string",
default: "",
},
},
dataDog: {
env: {
Expand Down
1 change: 1 addition & 0 deletions src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const REDIRECTION_SERVER_IPS = [
]
export const ALLOWED_DNS_ERROR_CODES = ["ENOTFOUND", "ENODATA"]

export const BUILT_WITH_ISOMER_LOGO = `<p class="footer-credits"><a href="https://www.isomer.gov.sg/" target="_blank" rel="noreferrer">Created with <img src="/assets/img/isomer_logo.svg" alt="Isomer Logo"></a>`
export const DNS_INDIRECTION_DOMAIN = "hostedon.isomer.gov.sg"
export const DNS_KEYCDN_SUFFIX = "kxcdn.com"
export const DNS_CNAME_SUFFIXES = ["cloudfront.net", DNS_KEYCDN_SUFFIX]
Expand Down
21 changes: 17 additions & 4 deletions src/monitoring/MonitoringWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import MonitoringError from "@root/errors/MonitoringError"
import { gb } from "@root/middleware/featureFlag"
import LaunchesService from "@root/services/identity/LaunchesService"
import { dnsMonitor } from "@root/utils/dns-utils"
import { isMonitoringEnabled } from "@root/utils/growthbook-utils"
import { getMonitoringConfig } from "@root/utils/growthbook-utils"
import promisifyPapaParse from "@root/utils/papa-parse"

const IsomerHostedDomainType = {
Expand Down Expand Up @@ -106,14 +106,16 @@ export default class MonitoringWorker {
* @returns List of redirection domains that are listed in the isomer-redirection repository
*/
getRedirectionDomains() {
const SYSTEM_GITHUB_TOKEN = config.get("github.systemToken")
const REDIRECTION_REPO_GITHUB_TOKEN = config.get(
"github.redirectionRepoGithubToken"
)
// seems to be a bug in typing, this is a direct
// copy paste from the octokit documentation
// https://octokit.github.io/rest.js/v20#automatic-retries
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const OctokitRetry = Octokit.plugin(retry as any)
const octokitWithRetry: Octokit = new OctokitRetry({
auth: SYSTEM_GITHUB_TOKEN,
auth: REDIRECTION_REPO_GITHUB_TOKEN,
request: { retries: 5 },
})

Expand Down Expand Up @@ -175,7 +177,9 @@ export default class MonitoringWorker {
}

driver() {
if (!isMonitoringEnabled(gb)) return okAsync("Monitoring Service disabled")
const monitoringConfig = getMonitoringConfig(gb)
if (!monitoringConfig.isEnabled)
return okAsync("Monitoring Service disabled")
const start = Date.now()
this.monitoringWorkerLogger.info("Monitoring service started")

Expand All @@ -196,6 +200,15 @@ export default class MonitoringWorker {
date: new Date(),
},
})
reportCardErr.map((err) =>
this.monitoringWorkerLogger.error({
message: "Error running monitoring service",
meta: {
dnsCheckerResult: err,
date: new Date(),
},
})
)
})
.orElse(() => okAsync([]))
.andThen(() => {
Expand Down
1 change: 0 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ import { mailer } from "@services/utilServices/MailClient"

import { apiLogger } from "./middleware/apiLogger"
import { NotificationOnEditHandler } from "./middleware/notificationOnEditHandler"
import MonitoringService from "./monitoring"
import getAuthenticatedSubrouter from "./routes/v2/authenticated"
import { ReviewsRouter } from "./routes/v2/authenticated/review"
import getAuthenticatedSitesSubrouter from "./routes/v2/authenticatedSites"
Expand Down
7 changes: 6 additions & 1 deletion src/types/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ export interface CloudmersiveConfigType {
timeout: number
}

export interface MonitoringConfig {
isEnabled: boolean
whitelistedRepos: string[]
}

export interface FeatureFlags {
is_build_times_reduction_enabled: boolean
is_ggs_enabled: boolean
is_show_staging_build_status_enabled: boolean
is_cloudmersive_enabled: CloudmersiveConfigType
is_local_diff_enabled: boolean
is_monitoring_enabled: boolean
is_monitoring_enabled: MonitoringConfig
}

// List of attributes we set in GrowthBook Instance in auth middleware
Expand Down
88 changes: 78 additions & 10 deletions src/utils/dns-utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import dns from "node:dns/promises"

import { err, ok, okAsync, Result, ResultAsync } from "neverthrow"
import { err, errAsync, ok, okAsync, Result, ResultAsync } from "neverthrow"

import {
BUILT_WITH_ISOMER_LOGO,
DNS_CNAME_SUFFIXES,
DNS_INDIRECTION_DOMAIN,
DNS_KEYCDN_SUFFIX,
REDIRECTION_SERVER_IPS,
} from "@root/constants"
import MonitoringError from "@root/errors/MonitoringError"
import logger from "@root/logger/logger"
import { gb } from "@root/middleware/featureFlag"

import { getMonitoringConfig } from "./growthbook-utils"

export function checkCname(domain: string) {
return ResultAsync.fromPromise(dns.resolveCname(domain), () => {
Expand All @@ -23,6 +28,11 @@ export function checkCname(domain: string) {
return okAsync(null)
}

//! todo: remove, this is some colima weird logic
if (cname.length === 1 && cname[0] === domain) {
return okAsync(null)
}

return okAsync(cname[0])
})
.orElse(() => okAsync(null))
Expand Down Expand Up @@ -118,7 +128,7 @@ export default function getDnsCheckerMessage(
)
}
if (isIntermediateValid && isRedirectionValid) {
return err(
return ok(
`The domain ${domain} is *all valid*! Although it is directly pointing to our CDN hosting provider and not the indirection layer. The apex domain \`${redirectionDomain}\` is also correctly configured to our redirection service.`
)
}
Expand Down Expand Up @@ -167,23 +177,36 @@ export default function getDnsCheckerMessage(
: "no A records"
}`
}

return err(responseMeta)
}

export function dnsMonitor(domain: string): ResultAsync<string, string> {
if (!domain) {
return okAsync("Empty domain provided")
}
return checkCname(domain)
.andThen((cname) => {
if (cname) {
return ResultAsync.combine([okAsync(domain), okAsync(cname)])
}
// Original domain does not have a CNAME record, check if the www
// version has a valid CNAME record
if (!cname && !domain.startsWith("www.")) {
const cnameDomain = `www.${domain}`
return ResultAsync.combine([
okAsync(cnameDomain),
checkCname(cnameDomain),
])
return checkCname(cnameDomain).andThen((cnameDomainRes) => {
if (cnameDomainRes) {
return ResultAsync.combine([
okAsync(cnameDomain),
okAsync(cnameDomainRes),
])
}
// this might be a redirection only domain
return ResultAsync.combine([okAsync(domain), okAsync(null)])
})
}

return ResultAsync.combine([okAsync(domain), okAsync(cname)])
// might be a redirection only domain
return ResultAsync.combine([okAsync(domain), okAsync(null)])
})
.andThen(([cnameDomain, cnameRecord]) => {
// Original and www version of the domain do not have a CNAME record,
Expand Down Expand Up @@ -245,8 +268,8 @@ export function dnsMonitor(domain: string): ResultAsync<string, string> {
redirectionDomain,
},
redirection,
]) =>
getDnsCheckerMessage(
]) => {
const dnsCheckerMessage = getDnsCheckerMessage(
domain,
cnameDomain,
redirectionDomain,
Expand All @@ -255,5 +278,50 @@ export function dnsMonitor(domain: string): ResultAsync<string, string> {
indirectionRecords,
redirection
)

const domainCheckerRes = dnsCheckerMessage.isOk()
? dnsCheckerMessage.value
: dnsCheckerMessage.error

return ResultAsync.fromPromise(
// using fetch as it has a convenient redirect option

fetch(`https://${domain}`),
() => new MonitoringError(`Failed to fetch site: ${domain}`)
)
.andThen((res) =>
ResultAsync.fromPromise(
res.text(),
() => new MonitoringError(`Failed to get text from response`)
)
)
.andThen((res) => {
const isStillHostingIsomerSite = res.includes(
BUILT_WITH_ISOMER_LOGO
)

if (isStillHostingIsomerSite) {
return okAsync(
`${domainCheckerRes}\nThe site is still being hosted with Isomer logo, and is likely still viewable by MOP`
)
}

const monitoringConfig = getMonitoringConfig(gb)

const isWeirdDomain = monitoringConfig.whitelistedRepos.includes(
domain
)
if (isWeirdDomain) {
return okAsync(
`${domainCheckerRes}\nThe site is not being hosted with Isomer logo, and is known to be an edge case.`
)
}
return errAsync(false)
})
.mapErr(
() =>
`${domainCheckerRes}\nThe site is **NOT** being hosted with Isomer logo, and is likely **NOT** viewable by MOP`
)
}
)
}
21 changes: 16 additions & 5 deletions src/utils/growthbook-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { GrowthBook, setPolyfills } from "@growthbook/growthbook"

import { config } from "@root/config/config"
import { FEATURE_FLAGS } from "@root/constants/featureFlags"
import { FeatureFlags, CloudmersiveConfigType } from "@root/types/featureFlags"
import {
FeatureFlags,
CloudmersiveConfigType,
MonitoringConfig,
} from "@root/types/featureFlags"

const GROWTHBOOK_API_HOST = "https://cdn.growthbook.io"

Expand Down Expand Up @@ -68,9 +72,16 @@ export const isCloudmersiveEnabled = (
)
}

export const isMonitoringEnabled = (
export const getMonitoringConfig = (
growthbook: GrowthBook<FeatureFlags> | undefined
): boolean => {
if (!growthbook) return true
return growthbook.getFeatureValue(FEATURE_FLAGS.IS_MONITORING_ENABLED, true)
): MonitoringConfig => {
const defaultConfig = {
isEnabled: false,
whitelistedRepos: [],
}
if (!growthbook) return defaultConfig
return growthbook.getFeatureValue(
FEATURE_FLAGS.IS_MONITORING_ENABLED,
defaultConfig
)
}
5 changes: 4 additions & 1 deletion support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useSharedMiddleware } from "@common/middleware"
import { config } from "@root/config/config"
import logger from "@root/logger/logger"
import MonitoringService from "@root/monitoring/MonitoringService"
import MonitoringWorker from "@root/monitoring/monitoringWorker"
import MonitoringWorker from "@root/monitoring/MonitoringWorker"

import { ROUTE_VERSION } from "./constants"
import { v2Router } from "./routes"
Expand All @@ -24,6 +24,9 @@ infraService.pollMessages()
export const monitoringWorker = new MonitoringWorker({
launchesService,
})
// dnsMonitor("isomer.gov.sg").mapErr(console.log).map(console.log)
// todo: remove after testing
monitoringWorker.driver()

export const monitoringService = new MonitoringService({
monitoringWorker,
Expand Down

0 comments on commit b5bbe94

Please sign in to comment.