Skip to content

Commit 840dc8f

Browse files
authored
Added password protection for initial setup (#59)
* First attempt to cook in a password * Fixed password setup as optional item, added AWS default password * lint fixes, apologies eslint, i meant no harm
1 parent 451b6ea commit 840dc8f

File tree

6 files changed

+94
-15
lines changed

6 files changed

+94
-15
lines changed

aws/build-an-deploy-aws.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,7 @@ aws ssm put-parameter --name wrongsecretvalue --overwrite --type SecureString --
106106

107107
wait
108108

109+
DEFAULT_PASSWORD=thankyou
109110
#TODO: REWRITE ABOVE, REWRITE THE HARDCODED DEPLOYMENT VALS INTO VALUES AND OVERRIDE THEM HERE!
110-
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"
111+
echo "default password is ${DEFAULT_PASSWORD}"
112+
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"

helm/wrongsecrets-ctf-party/templates/wrongsecrets-balancer/deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ spec:
4444
env:
4545
- name: REACT_APP_MOVING_GIF_LOGO
4646
value: {{ .Values.balancer.env.REACT_APP_MOVING_GIF_LOGO }}
47+
- name: REACT_APP_ACCESS_PASSWORD
48+
value: {{ .Values.balancer.env.REACT_APP_ACCESS_PASSWORD }}
4749
- name: REACT_APP_HEROKU_WRONGSECRETS_URL
4850
value: {{ .Values.balancer.env.REACT_APP_HEROKU_WRONGSECRETS_URL }}
4951
- name: REACT_APP_CTFD_URL

helm/wrongsecrets-ctf-party/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ balancer:
6868
REACT_APP_CTFD_URL : 'https://ctfd.io'
6969
REACT_APP_S3_BUCKET_URL : 's3://funstuff'
7070
K8S_ENV: 'k8s' #oraws
71+
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...
7172
metrics:
7273
# -- enables prometheus metrics for the balancer. If set to true you should change the prometheus-scraper password
7374
enabled: true

wrongsecrets-balancer/src/app.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const basicAuth = require('basic-auth-connect');
77
const onFinished = require('on-finished');
88

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

1112
const app = express();
1213

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

54-
app.get('/balancer/dynamics', (req, res) =>
55+
app.get('/balancer/dynamics', (req, res) => {
56+
const accessPassword = process.env['REACT_APP_ACCESS_PASSWORD'];
57+
logger.info(`password: ${accessPassword}`);
58+
var usePassword = false;
59+
if (!accessPassword || accessPassword.length === 0) {
60+
//nothign for now
61+
} else {
62+
usePassword = true;
63+
}
5564
res.json({
5665
react_gif_logo: process.env['REACT_APP_MOVING_GIF_LOGO'],
5766
heroku_wrongsecret_ctf_url: process.env['REACT_APP_HEROKU_WRONGSECRETS_URL'],
5867
ctfd_url: process.env['REACT_APP_CTFD_URL'],
5968
s3_bucket_url: process.env['REACT_APP_S3_BUCKET_URL'],
60-
})
61-
);
69+
enable_password: usePassword,
70+
});
71+
});
6272

6373
app.use(cookieParser(get('cookieParser.secret')));
6474
app.use('/balancer', express.json());

wrongsecrets-balancer/src/teams/teams.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const cryptoRandomString = require('crypto-random-string');
66
const Joi = require('@hapi/joi');
77
const expressJoiValidation = require('express-joi-validation');
88
const promClient = require('prom-client');
9+
const accessPassword = process.env.REACT_APP_ACCESS_PASSWORD;
910

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

111+
/**
112+
* @param {import("express").Request} req
113+
* @param {import("express").Response} res
114+
* @param {import("express").NextFunction} next
115+
*/
116+
async function validatePassword(req, res, next) {
117+
const { team } = req.params;
118+
const { password } = req.body;
119+
logger.info(
120+
`checking password for team ${team}, submitted: ${password}, needed: ${accessPassword}`
121+
);
122+
try {
123+
if (!accessPassword || accessPassword.length === 0) {
124+
next();
125+
} else {
126+
if (password === accessPassword) {
127+
next();
128+
} else {
129+
res.status(403).send({ message: 'Go home pizzaboy!' });
130+
}
131+
}
132+
} catch (error) {
133+
res.status(500).send({ message: 'Go home pizzaboy!' });
134+
}
135+
}
136+
110137
/**
111138
* @param {import("express").Request} req
112139
* @param {import("express").Response} res
@@ -531,6 +558,7 @@ const paramsSchema = Joi.object({
531558
const bodySchema = Joi.object({
532559
hmacvalue: Joi.string().hex().length(64),
533560
passcode: Joi.string().alphanum().uppercase().length(8),
561+
password: Joi.string().alphanum().max(64),
534562
});
535563

536564
router.post('/logout', logout);
@@ -542,6 +570,7 @@ router.post(
542570
interceptAdminLogin,
543571
joinIfTeamAlreadyExists,
544572
checkIfMaxJuiceShopInstancesIsReached,
573+
validatePassword,
545574
validateHMAC,
546575
createTeam
547576
);

wrongsecrets-balancer/ui/src/pages/JoinPage.js

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ const messages = defineMessages({
1616
id: 'teamname_validation_constraints',
1717
defaultMessage: "Teamnames must consist of lowercase letter, number or '-'",
1818
},
19+
passwordValidationConstraints: {
20+
id: 'password_validation_constraints',
21+
defaultMessage: 'Passwords must consist of alphanumeric characters only',
22+
},
1923
});
2024

2125
const CenterLogo = styled.img`
@@ -27,6 +31,7 @@ const CenterLogo = styled.img`
2731

2832
export const JoinPage = injectIntl(({ intl }) => {
2933
const [teamname, setTeamname] = useState('');
34+
const [password, setPassword] = useState('');
3035
const [failed, setFailed] = useState(false);
3136
const navigate = useNavigate();
3237
const location = useLocation();
@@ -46,8 +51,18 @@ export const JoinPage = injectIntl(({ intl }) => {
4651
const { formatMessage } = intl;
4752

4853
async function sendJoinRequest() {
49-
if (window.confirm('Are you ready?')) {
50-
try {
54+
try {
55+
if (dynamics.enable_password) {
56+
const hmacvalue = cryptoJS
57+
.HmacSHA256(`${teamname}`, 'hardcodedkey')
58+
.toString(cryptoJS.enc.Hex);
59+
const { data } = await axios.post(`/balancer/teams/${teamname}/join`, {
60+
passcode,
61+
hmacvalue,
62+
password,
63+
});
64+
navigate(`/teams/${teamname}/joined/`, { state: { passcode: data.passcode } });
65+
} else {
5166
const hmacvalue = cryptoJS
5267
.HmacSHA256(`${teamname}`, 'hardcodedkey')
5368
.toString(cryptoJS.enc.Hex);
@@ -56,15 +71,15 @@ export const JoinPage = injectIntl(({ intl }) => {
5671
hmacvalue,
5772
});
5873
navigate(`/teams/${teamname}/joined/`, { state: { passcode: data.passcode } });
59-
} catch (error) {
60-
if (
61-
error.response.status === 401 &&
62-
error.response.data.message === 'Team requires authentication to join'
63-
) {
64-
navigate(`/teams/${teamname}/joining/`);
65-
} else {
66-
setFailed(true);
67-
}
74+
}
75+
} catch (error) {
76+
if (
77+
error.response.status === 401 &&
78+
error.response.data.message === 'Team requires authentication to join'
79+
) {
80+
navigate(`/teams/${teamname}/joining/`);
81+
} else {
82+
setFailed(true);
6883
}
6984
}
7085
}
@@ -80,6 +95,7 @@ export const JoinPage = injectIntl(({ intl }) => {
8095
heroku_wrongsecret_ctf_url: process.env['REACT_APP_HEROKU_WRONGSECRETS_URL'],
8196
ctfd_url: process.env['REACT_APP_CTFD_URL'],
8297
s3_bucket_url: process.env['REACT_APP_S3_BUCKET_URL'],
98+
enable_password: false,
8399
};
84100

85101
const [dynamics, setDynamics] = useState(initialDynamics);
@@ -185,6 +201,25 @@ export const JoinPage = injectIntl(({ intl }) => {
185201
maxLength="16"
186202
onChange={({ target }) => setTeamname(target.value)}
187203
/>
204+
{dynamics.enable_password ? (
205+
<p>
206+
<Label htmlFor="password">
207+
<FormattedMessage id="password" defaultMessage="Password" />
208+
</Label>
209+
<Input
210+
type="text"
211+
id="password"
212+
data-test-id="password-input"
213+
name="password"
214+
disabled={!dynamics.enable_password}
215+
value={password}
216+
title={formatMessage(messages.passwordValidationConstraints)}
217+
pattern="^[a-zA-Z0-9]([-a-z-A-Z0-9])+[a-zA-Z0-9]$"
218+
maxLength="64"
219+
onChange={({ target }) => setPassword(target.value)}
220+
/>
221+
</p>
222+
) : null}
188223
<Button data-test-id="create-join-team-button" type="submit">
189224
<FormattedMessage id="create_or_join_team_label" defaultMessage="Create / Join Team" />
190225
</Button>

0 commit comments

Comments
 (0)