Skip to content

fix: scaffold challenge 48 sealed secret #903

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build-and-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ WRONGSECRETS_IMAGE=$(cat helm/wrongsecrets-ctf-party/values.yaml | yq '.wrongsec
WRONGSECRETS_TAG=$(cat helm/wrongsecrets-ctf-party/values.yaml | yq '.wrongsecrets.tag')
WEBTOP_IMAGE=$(cat helm/wrongsecrets-ctf-party/values.yaml | yq '.virtualdesktop.image')
WEBTOP_TAG=$(cat helm/wrongsecrets-ctf-party/values.yaml | yq '.virtualdesktop.tag')
echo "doing workaround for selaed secrets"
echo "doing workaround for sealed secrets"
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install ws-sealedsecrets sealed-secrets/sealed-secrets --namespace kube-system
helm install ws-sealedsecrets sealed-secrets/sealed-secrets --namespace kube-system --set-string fullnameOverride=sealed-secrets-controller
echo "Pulling in required images to actually run $WRONGSECRETS_IMAGE:$WRONGSECRETS_TAG & $WEBTOP_IMAGE:$WEBTOP_TAG."
echo "If you see an authentication failure: pull them manually by the following 2 commands"
echo "'docker pull $WRONGSECRETS_IMAGE:$WRONGSECRETS_TAG'"
Expand Down
2 changes: 1 addition & 1 deletion cleaner/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:20-alpine as build
FROM node:20-alpine AS build
RUN mkdir -p /home/app
WORKDIR /home/app
COPY package.json package-lock.json ./
Expand Down
18 changes: 18 additions & 0 deletions wrongsecrets-balancer/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions wrongsecrets-balancer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
"joi": "^17.13.3",
"lodash": "^4.17.21",
"minimatch": "^10.0.1",
"node-forge": "^1.3.1",
"on-finished": "^2.4.1",
"prom-client": "^15.1.3",
"winston": "^3.17.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.14.0",
"@types/bcryptjs": "^2.4.6",
"cookie-signature": "^1.2.2",
"eslint": "^9.14.0",
"eslint-plugin-prettier": "^5.2.1",
Expand Down
74 changes: 44 additions & 30 deletions wrongsecrets-balancer/src/kubernetes.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const {
NetworkingV1Api,
} = require('@kubernetes/client-node');

// Crypto library for sealed secret
const forge = require('node-forge');

const kc = new KubeConfig();
kc.loadFromCluster();

Expand Down Expand Up @@ -129,7 +132,7 @@ const checkSealedSecretsController = async () => {
const response = await safeApiCall(
() =>
k8sAppsApi.readNamespacedDeployment({
name: 'ws-sealedsecrets-sealed-secrets',
name: 'sealed-secrets-controller',
namespace: 'kube-system',
}),
'Check Sealed Secrets controller'
Expand Down Expand Up @@ -251,6 +254,29 @@ const createChallenge33SecretForTeam = async (team) => {
});
};

/**
* @param {string} value - the value to be sealed
*/
const sealSecret = async (value) => {
const sealedSecretCert = await getSealedSecretsPublicKey();
const cert = forge.pki.certificateFromPem(sealedSecretCert);
const key = cert.publicKey;
const encrypted = key.encrypt(value, 'RSA-OAEP', {
md: forge.md.sha256.create(),
mgf1: { md: forge.md.sha1.create() },
});
return Buffer.from(encrypted, 'binary').toString('base64');
};

/**
* @param {string} team - The team name
* @param {string} value - The challenge 48 secret
*/
const createChallenge48SecretForTeam = async (team, value) => {
const secretValue = await sealSecret(value);
createSealedSecretForTeam(team, 'challenge48secret', { secret: secretValue });
};

/**
* Create a SealedSecret in the team's namespace for secure secret management
* @param {string} team - The team name
Expand All @@ -259,8 +285,7 @@ const createChallenge33SecretForTeam = async (team) => {
*/
const createSealedSecretForTeam = async (team, secretName, secretData) => {
try {
// Note: In production, you would seal the data using kubeseal CLI or the controller's public key
// For this example, we're creating a template that would need to be sealed externally
logger.info(`Secret name: ${secretName}, Secret data: ${JSON.stringify(secretData)}`);
const sealedSecretManifest = {
apiVersion: 'bitnami.com/v1alpha1',
kind: 'SealedSecret',
Expand All @@ -272,6 +297,9 @@ const createSealedSecretForTeam = async (team, secretName, secretData) => {
'app.kubernetes.io/instance': `wrongsecrets-${team}`,
'app.kubernetes.io/part-of': 'wrongsecrets-ctf-party',
},
annotations: {
'sealedsecrets.bitnami.com/namespace-wide': 'true',
},
},
spec: {
template: {
Expand All @@ -282,10 +310,13 @@ const createSealedSecretForTeam = async (team, secretName, secretData) => {
'app.kubernetes.io/name': 'wrongsecrets',
'app.kubernetes.io/instance': `wrongsecrets-${team}`,
},
annotations: {
'sealedsecrets.bitnami.com/namespace-wide': 'true',
},
},
type: 'Opaque',
},
encryptedData: secretData, // This should be pre-sealed data
encryptedData: secretData,
},
};

Expand All @@ -305,31 +336,22 @@ const createSealedSecretForTeam = async (team, secretName, secretData) => {
}
};

/**
* Create a sealed secret for challenge 33 specific to the team
* TODO: REPLACE WITH CHALLENGE 53 FOR ACTUAL SEALED SECRET
* @param {string} team - The team name
*/
const createSealedChallenge33SecretForTeam = async (team) => {
const secretName = 'challenge33';
const secretData = {
// Note: These values should be sealed using kubeseal before deployment
answer: challenge33Value || 'default-challenge33-value',
};

return createSealedSecretForTeam(team, secretName, secretData);
};

/**
* Get the Sealed Secrets controller public key for sealing secrets
*/
const getSealedSecretsPublicKey = async () => {
try {
const list = await k8sCoreApi.listNamespacedSecret({
namespace: 'kube-system',
labelSelector: 'sealedsecrets.bitnami.com/sealed-secrets-key=active',
limit: 1,
});
const secretName = list.items.map((secret) => secret.metadata.name).find((name) => name);
const response = await k8sCoreApi.readNamespacedSecret({
name: 'sealed-secrets-key',
name: secretName,
namespace: 'kube-system',
});
return response.data['tls.crt'];
return Buffer.from(response.data['tls.crt'], 'base64').toString('utf-8');
} catch (error) {
logger.error('Failed to get Sealed Secrets public key:', error.body || error);
throw new Error(`Failed to get public key: ${error.message}`);
Expand Down Expand Up @@ -557,14 +579,6 @@ const deleteChallenge53DeploymentForTeam = async (team) => {
* Enhanced deployment creation with SealedSecret integration
*/
const createK8sDeploymentForTeam = async ({ team, passcodeHash }) => {
// Check if we should use SealedSecrets
const useSealedSecrets = await checkSealedSecretsController();

if (useSealedSecrets) {
// Create sealed secrets for the team
await createSealedChallenge33SecretForTeam(team);
}

const deploymentWrongSecretsConfig = {
metadata: {
namespace: `t-${team}`,
Expand Down Expand Up @@ -2517,13 +2531,13 @@ module.exports = {
createSecretsfileForTeam,
createChallenge33SecretForTeam,
createSealedSecretForTeam,
createSealedChallenge33SecretForTeam,
getSealedSecretsPublicKey,
createNameSpaceForTeam,
createK8sDeploymentForTeam,
createK8sChallenge53DeploymentForTeam,
getChallenge53InstanceForTeam,
deleteChallenge53DeploymentForTeam,
createChallenge48SecretForTeam,

// AWS functions
createAWSSecretsProviderForTeam,
Expand Down
12 changes: 12 additions & 0 deletions wrongsecrets-balancer/src/teams/teams.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const promClient = require('prom-client');
const accessPassword = process.env.REACT_APP_ACCESS_PASSWORD;
const hmac_key = process.env.REACT_APP_CREATE_TEAM_HMAC_KEY || 'hardcodedkey';

const challenge48secret = cryptoRandomString({ length: 32 }).toUpperCase();

const validator = expressJoiValidation.createValidator();
const k8sEnv = process.env.K8S_ENV || 'k8s';
const router = express.Router();
Expand Down Expand Up @@ -40,6 +42,7 @@ const {
createRoleBindingForWebtop,
createNSPsforTeam,
createK8sChallenge53DeploymentForTeam,
createChallenge48SecretForTeam,
} = require('../kubernetes');

const loginCounter = new promClient.Counter({
Expand Down Expand Up @@ -300,6 +303,15 @@ async function createTeam(req, res) {
logger.error(`Error while creating challenge33 secretsfile ${team}: ${error}`);
res.status(500).send({ message: 'Failed to Create Instance' });
}

try {
logger.info(`Creating challenge48 secret for team '${team}'`);
await createChallenge48SecretForTeam(team, challenge48secret);
} catch (error) {
logger.error(`Error while creating challenge48 secret ${team}: ${error}`);
res.status(500).send({ message: 'Failed to Create Instance' });
}

try {
logger.info(`Creating WrongSecrets Deployment for team '${team}' with k8s (no cloud)`);
await createK8sDeploymentForTeam({ team, passcodeHash: hash });
Expand Down
Loading