Skip to content

Added password protection for initial setup #59

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

Merged
merged 3 commits into from
Sep 26, 2022
Merged
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: 3 additions & 1 deletion aws/build-an-deploy-aws.sh
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,7 @@ aws ssm put-parameter --name wrongsecretvalue --overwrite --type SecureString --

wait

DEFAULT_PASSWORD=thankyou
#TODO: REWRITE ABOVE, REWRITE THE HARDCODED DEPLOYMENT VALS INTO VALUES AND OVERRIDE THEM HERE!
helm upgrade --install mj ./helm/wrongsecrets-ctf-party --set="imagePullPolicy=Always" --set="balancer.env.K8S_ENV=aws" --set="balancer.cookie.cookieParserSecret=thisisanewrandomvaluesowecanworkatit" --set="balancer.repository=jeroenwillemsen/wrongsecrets-balancer" --set="balancer.tag=0.86aws" --set="balancer.replicas=4" --set="wrongsecretsCleanup.repository=jeroenwillemsen/wrongsecrets-ctf-cleaner" --set="wrongsecretsCleanup.tag=0.2"
echo "default password is ${DEFAULT_PASSWORD}"
helm upgrade --install mj ./helm/wrongsecrets-ctf-party --set="imagePullPolicy=Always" --set="balancer.env.K8S_ENV=aws" --set="balancer.env.REACT_APP_ACCESS_PASSWORD=${DEFAULT_PASSWORD}" --set="balancer.cookie.cookieParserSecret=thisisanewrandomvaluesowecanworkatit" --set="balancer.repository=jeroenwillemsen/wrongsecrets-balancer" --set="balancer.tag=0.86aws" --set="balancer.replicas=4" --set="wrongsecretsCleanup.repository=jeroenwillemsen/wrongsecrets-ctf-cleaner" --set="wrongsecretsCleanup.tag=0.2"
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ spec:
env:
- name: REACT_APP_MOVING_GIF_LOGO
value: {{ .Values.balancer.env.REACT_APP_MOVING_GIF_LOGO }}
- name: REACT_APP_ACCESS_PASSWORD
value: {{ .Values.balancer.env.REACT_APP_ACCESS_PASSWORD }}
- name: REACT_APP_HEROKU_WRONGSECRETS_URL
value: {{ .Values.balancer.env.REACT_APP_HEROKU_WRONGSECRETS_URL }}
- name: REACT_APP_CTFD_URL
Expand Down
1 change: 1 addition & 0 deletions helm/wrongsecrets-ctf-party/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ balancer:
REACT_APP_CTFD_URL : 'https://ctfd.io'
REACT_APP_S3_BUCKET_URL : 's3://funstuff'
K8S_ENV: 'k8s' #oraws
REACT_APP_ACCESS_PASSWORD: '' #DEFAULT NO PASSWORD, PLAYING THIS IN PUBLIC? PUT A FANCY STRING HERE, BUT BE GENTLE: USERS NEED TO BE ABLE TO COPY THAT STUFF...
metrics:
# -- enables prometheus metrics for the balancer. If set to true you should change the prometheus-scraper password
enabled: true
Expand Down
16 changes: 13 additions & 3 deletions wrongsecrets-balancer/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const basicAuth = require('basic-auth-connect');
const onFinished = require('on-finished');

const { get, extractTeamName } = require('./config');
const { logger } = require('./logger');

const app = express();

Expand Down Expand Up @@ -51,14 +52,23 @@ const adminRoutes = require('./admin/admin');
const proxyRoutes = require('./proxy/proxy');
const scoreBoard = require('./score-board/score-board');

app.get('/balancer/dynamics', (req, res) =>
app.get('/balancer/dynamics', (req, res) => {
const accessPassword = process.env['REACT_APP_ACCESS_PASSWORD'];
logger.info(`password: ${accessPassword}`);
var usePassword = false;
if (!accessPassword || accessPassword.length === 0) {
//nothign for now
} else {
usePassword = true;
}
res.json({
react_gif_logo: process.env['REACT_APP_MOVING_GIF_LOGO'],
heroku_wrongsecret_ctf_url: process.env['REACT_APP_HEROKU_WRONGSECRETS_URL'],
ctfd_url: process.env['REACT_APP_CTFD_URL'],
s3_bucket_url: process.env['REACT_APP_S3_BUCKET_URL'],
})
);
enable_password: usePassword,
});
});

app.use(cookieParser(get('cookieParser.secret')));
app.use('/balancer', express.json());
Expand Down
29 changes: 29 additions & 0 deletions wrongsecrets-balancer/src/teams/teams.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const cryptoRandomString = require('crypto-random-string');
const Joi = require('@hapi/joi');
const expressJoiValidation = require('express-joi-validation');
const promClient = require('prom-client');
const accessPassword = process.env.REACT_APP_ACCESS_PASSWORD;

const validator = expressJoiValidation.createValidator();
const k8sEnv = process.env.K8S_ENV || 'k8s';
Expand Down Expand Up @@ -107,6 +108,32 @@ async function validateHMAC(req, res, next) {
}
}

/**
* @param {import("express").Request} req
* @param {import("express").Response} res
* @param {import("express").NextFunction} next
*/
async function validatePassword(req, res, next) {
const { team } = req.params;
const { password } = req.body;
logger.info(
`checking password for team ${team}, submitted: ${password}, needed: ${accessPassword}`
);
try {
if (!accessPassword || accessPassword.length === 0) {
next();
} else {
if (password === accessPassword) {
next();
} else {
res.status(403).send({ message: 'Go home pizzaboy!' });
}
}
} catch (error) {
res.status(500).send({ message: 'Go home pizzaboy!' });
}
}

/**
* @param {import("express").Request} req
* @param {import("express").Response} res
Expand Down Expand Up @@ -531,6 +558,7 @@ const paramsSchema = Joi.object({
const bodySchema = Joi.object({
hmacvalue: Joi.string().hex().length(64),
passcode: Joi.string().alphanum().uppercase().length(8),
password: Joi.string().alphanum().max(64),
});

router.post('/logout', logout);
Expand All @@ -542,6 +570,7 @@ router.post(
interceptAdminLogin,
joinIfTeamAlreadyExists,
checkIfMaxJuiceShopInstancesIsReached,
validatePassword,
validateHMAC,
createTeam
);
Expand Down
57 changes: 46 additions & 11 deletions wrongsecrets-balancer/ui/src/pages/JoinPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const messages = defineMessages({
id: 'teamname_validation_constraints',
defaultMessage: "Teamnames must consist of lowercase letter, number or '-'",
},
passwordValidationConstraints: {
id: 'password_validation_constraints',
defaultMessage: 'Passwords must consist of alphanumeric characters only',
},
});

const CenterLogo = styled.img`
Expand All @@ -27,6 +31,7 @@ const CenterLogo = styled.img`

export const JoinPage = injectIntl(({ intl }) => {
const [teamname, setTeamname] = useState('');
const [password, setPassword] = useState('');
const [failed, setFailed] = useState(false);
const navigate = useNavigate();
const location = useLocation();
Expand All @@ -46,8 +51,18 @@ export const JoinPage = injectIntl(({ intl }) => {
const { formatMessage } = intl;

async function sendJoinRequest() {
if (window.confirm('Are you ready?')) {
try {
try {
if (dynamics.enable_password) {
const hmacvalue = cryptoJS
.HmacSHA256(`${teamname}`, 'hardcodedkey')
.toString(cryptoJS.enc.Hex);
const { data } = await axios.post(`/balancer/teams/${teamname}/join`, {
passcode,
hmacvalue,
password,
});
navigate(`/teams/${teamname}/joined/`, { state: { passcode: data.passcode } });
} else {
const hmacvalue = cryptoJS
.HmacSHA256(`${teamname}`, 'hardcodedkey')
.toString(cryptoJS.enc.Hex);
Expand All @@ -56,15 +71,15 @@ export const JoinPage = injectIntl(({ intl }) => {
hmacvalue,
});
navigate(`/teams/${teamname}/joined/`, { state: { passcode: data.passcode } });
} catch (error) {
if (
error.response.status === 401 &&
error.response.data.message === 'Team requires authentication to join'
) {
navigate(`/teams/${teamname}/joining/`);
} else {
setFailed(true);
}
}
} catch (error) {
if (
error.response.status === 401 &&
error.response.data.message === 'Team requires authentication to join'
) {
navigate(`/teams/${teamname}/joining/`);
} else {
setFailed(true);
}
}
}
Expand All @@ -80,6 +95,7 @@ export const JoinPage = injectIntl(({ intl }) => {
heroku_wrongsecret_ctf_url: process.env['REACT_APP_HEROKU_WRONGSECRETS_URL'],
ctfd_url: process.env['REACT_APP_CTFD_URL'],
s3_bucket_url: process.env['REACT_APP_S3_BUCKET_URL'],
enable_password: false,
};

const [dynamics, setDynamics] = useState(initialDynamics);
Expand Down Expand Up @@ -185,6 +201,25 @@ export const JoinPage = injectIntl(({ intl }) => {
maxLength="16"
onChange={({ target }) => setTeamname(target.value)}
/>
{dynamics.enable_password ? (
<p>
<Label htmlFor="password">
<FormattedMessage id="password" defaultMessage="Password" />
</Label>
<Input
type="text"
id="password"
data-test-id="password-input"
name="password"
disabled={!dynamics.enable_password}
value={password}
title={formatMessage(messages.passwordValidationConstraints)}
pattern="^[a-zA-Z0-9]([-a-z-A-Z0-9])+[a-zA-Z0-9]$"
maxLength="64"
onChange={({ target }) => setPassword(target.value)}
/>
</p>
) : null}
<Button data-test-id="create-join-team-button" type="submit">
<FormattedMessage id="create_or_join_team_label" defaultMessage="Create / Join Team" />
</Button>
Expand Down