Skip to content

Commit 52868d1

Browse files
committed
Adding support for open and closed registrations.
- Controlled by a deploy.py argument, which feeds into the CF template, and the env var of the Register lambda. - This handles how new registration requests are handled, but that's about it.
1 parent 884a97a commit 52868d1

File tree

4 files changed

+123
-51
lines changed

4 files changed

+123
-51
lines changed

Register.py

+91-48
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
HMAC_SECRET = os.environ["HMAC_SECRET"].encode("utf-8")
1919
API_URL = os.environ["API_URL"]
2020

21+
# This will be "open" or "closed", which determins how email address that don't exist in the
22+
# registrants table are handled.
23+
REGISTRATION_MODE = os.environ["REGISTRATION_MODE"].lower()
24+
2125

2226
def sign_payload(data):
2327
sig = hmac.new(HMAC_SECRET, data.encode("utf-8"),
@@ -56,6 +60,9 @@ def post(event, contest):
5660

5761
try:
5862
username = event["username"]
63+
# In closed registration, this may pre-assigned, but we still want to ask for a username
64+
# so we can let them down and confuse the registrants. :)
65+
display_name = username
5966
except:
6067
return {"result": "failure"}
6168

@@ -79,25 +86,48 @@ def post(event, contest):
7986
"S": email
8087
}})
8188

82-
# If the DDB item doesn't exist, or if it does and it hasn't yet been assigned a team ID
83-
# then there is no duplicate problem.
84-
if "Item" in item and "teamId" in item["Item"]:
85-
return {"result": "failed_duplicate"}
89+
# Registrations are duplicates if the email exists in the DDB table, and the "confirmationTime" column
90+
# doesn't yet have a timestamp.
91+
#
92+
# Obvious, if the item is missing, the get_item() call returns no Item key.
93+
if "Item" in item:
94+
# Regardless of registration mode, if there is a confirmed registration for this email,
95+
# don't permit another.
96+
if "confirmationTime" in item["Item"]:
97+
return {"result": "failed_duplicate"}
98+
else:
99+
# This is the case where the email exists, in which case the registration mode matters.
100+
101+
# The default behaviour is open registration.
102+
# For closed registration, they needn't have a team ID, but they might, so if they do
103+
# have one from the table already, use that.
104+
if REGISTRATION_MODE == "closed":
105+
team_id = int(item["Item"].get("team_id", {"N": team_id})["N"])
106+
display_name = item["Item"].get("display_name", username)["S"]
107+
else: # REGISTRATION_MODE == "open", or any other value.
108+
pass
109+
else:
110+
# In the case of closed registration, every email that tries to register must be part of
111+
# the table already. If it isn't, it gets an "unauthorized" error response, but to prevent
112+
# people trying to credential-stuff, we'll just say "unknown". 😺
113+
if REGISTRATION_MODE == "closed":
114+
return {"result": "failed_unknown"}
86115

87116
confirmation_payload = {
88117
"email": email,
89118
"username": username,
90119
"teamId": team_id,
91-
"registrationTime": time.time()
120+
"registrationTime": time.time(),
121+
"display_name": display_name
92122
}
93123

94124
confirmation_token = sign_payload(json.dumps(confirmation_payload))
95125

96126
ses.send_email(Destination={"ToAddresses": [email]},
97-
Source="The Big Kahuna CTF <ctf@example.com>",
127+
Source="CTF Registration <%s>" % os.environ["SES_EMAIL_SOURCE"],
98128
Message={
99129
"Subject": {
100-
"Data": "The Big Kahuna CTF - Confirm your email"
130+
"Data": "CTF Registration - Confirm your email"
101131
},
102132
"Body": {
103133
"Html": {
@@ -130,9 +160,9 @@ def post(event, contest):
130160
<body class="vscode-light">
131161
<h1 id="ctf-registration">CTF Registration - Confirm your email</h1>
132162
<p>You've taken the first step towards registering in the CTF. We just need you to confirm your email, and we'll be on our way.</p>
133-
<p>Click the <a href="%s">here</a> to confirm your registration.</p>
163+
<p>Click <a href="%s">here</a> to confirm your registration.</p>
134164
<p>See you soon!</p>
135-
<p>The Big Kahuna CTF Team</p>
165+
<p>The CTF Team</p>
136166
</body>
137167
</html>
138168
""" %
@@ -159,6 +189,7 @@ def get_confirm(event, context):
159189
email = registration_payload["email"]
160190
team_id = registration_payload["teamId"]
161191
username = registration_payload["username"]
192+
display_name = registration_payload["display_name"]
162193
registration_time = registration_payload["registrationTime"]
163194

164195
ddb.put_item(TableName=event["RegistrantsTable"],
@@ -169,6 +200,9 @@ def get_confirm(event, context):
169200
"username": {
170201
"S": username
171202
},
203+
"display_name": {
204+
"S": display_name
205+
},
172206
"teamId": {
173207
"N": str(team_id)
174208
},
@@ -181,53 +215,61 @@ def get_confirm(event, context):
181215
})
182216

183217
ses.send_email(Destination={"ToAddresses": [email]},
184-
Source="The Big Kahuna CTF <ctf@example.com>",
218+
Source="CTF Registration <%s>" % os.environ["SES_EMAIL_SOURCE"],
185219
Message={
186220
"Subject": {
187-
"Data": "The Big Kahuna CTF Registration"
221+
"Data": "CTF Registration - Complete!"
188222
},
189223
"Body": {
190224
"Html": {
191225
"Charset":
192226
"utf-8",
193227
"Data":
194-
"""
228+
f"""
195229
<!DOCTYPE html>
196230
<html>
197-
<head>
198-
<meta charset="UTF-8">
199-
<title>CTF Registration</title>
200-
201-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/markdown.css">
202-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/highlight.css">
203231
204-
<style>
205-
.task-list-item { list-style-type: none; } .task-list-item-checkbox { margin-left: -20px; vertical-align: middle; }
206-
</style>
207-
<style>
208-
body {
209-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif;
210-
font-size: 14px;
211-
line-height: 1.6;
212-
}
213-
</style>
214-
215-
</head>
216-
<body class="vscode-light">
217-
<h1 id="ctf-registration">CTF Registration</h1>
218-
<p>Congratulations %s! You've signed up for our CTF, The Big Kahuna CTF!</p>
219-
<ul>
220-
<li>When submitting your flags, your team ID is %d and you will show up as &quot;%s&quot; on the scores list</li>
221-
<li>To submit flags, head over to <a href="https://ctf.example.com/submit">https://ctf.example.com/submit</a></li>
222-
<li>To view the current standings, head over to <a href="https://ctf.example.com/scores">https://ctf.example.com/scores</a></li>
223-
</ul>
224-
<p>Enjoy and godspeed!</p>
225-
<p>Best wishes,
226-
The Big Kahuna CTF Team</p>
227-
228-
</body>
229-
</html>
230-
""" % (username, team_id, username)
232+
<body>
233+
<h1>Welcome to the 2019 CCDC event!</h1>
234+
<p>To get you started, here's some important information and references. <ul>
235+
<li>Your team name is "{display_name}"", and we have auto-generated a team ID for your team.</li>
236+
<li>Your team ID is {team_id}</li>
237+
<li>You'll need this team ID when submitting flags, <i>don't lose this</i>! To submit flags, you will need
238+
to use the <a href="https://ccdc.zone/scores/submit">flag submission page</a>. </li>
239+
<li>To see the scoreboard, go to the <a href="https://ccdc.zone/scores/dashboard">score dashboard</a></li>
240+
<li>This team ID is unique to your team, so don't share it with members of other teams.</li>
241+
</ul>
242+
</p>
243+
<p>Your team has been allocated an environment for today, complete with targets, as well as VMs for you to remote
244+
into that will contain all of the tools you will need today. You can find your team's starting package <a
245+
href="https://ccdc.zone/packages/{team_id}.zip">here</a>. <ul>
246+
<li>This package contains passwords, private keys, hostnames, and other information to get you and your
247+
teammates started.</li>
248+
<li>Inside of the zip package, you'll see a few files that start with the word `jumpbox`. These files
249+
contain the details for access your team's jumpboxes (<a target="_blank"
250+
href="https://en.wikipedia.org/wiki/Jump_server">Ref</a>). There are 5 jumpboxes for your team, so
251+
you and your teammates can decide who gets which jumpbox (they are numbered for your convenience). </li>
252+
<li><b>THE JUMP BOXES ARE NOT IN SCOPE OF THE EVENT.</b> They are provided as ready-made environments,
253+
complete with all of the tools you will need during the event. If there are tools you want to use that
254+
are not installed in the jumpboxes, you are welcome to install them (you have full Administrator
255+
credentials to these jumpboxes), however <i>you do so at your own risk</i>. <b>IF YOU LOCK YOURSELF OUT
256+
OF YOUR JUMPBOX, WE WILL NOT PROVISION A NEW ONE AND YOU WILL NEED TO DEPEND ON YOUR TEAMMATES</b>.
257+
</li>
258+
<li>To access your chosen jumpbox, you will need to RDP into it using the hostname and credentials in the
259+
corresponding file. Microsoft has documentation on how their applications (<a target="_blank"
260+
href="https://docs.microsoft.com/en-us/windows-server/remote/remote-desktop-services/clients/remote-desktop-clients">Ref</a>),
261+
and for those on Linux we recommend using the <a target="_blank" href="https://remmina.org/">Remmina
262+
remote desktop client</a>.</li>
263+
</ul>
264+
</p>
265+
<p> This should be enough to get you started, but you will need to read the <a href="https://ccdc.zone/documentation">official
266+
complete documentation</a> for all of the information you will need to be successful today. </p>
267+
<h2>Keep your email inbox open or notifications turned on, as we may be sending updates, hints, and notifications to
268+
you by email throughout the day.</h2>
269+
270+
</html>
271+
</body>
272+
"""
231273
}
232274
}
233275
})
@@ -237,10 +279,11 @@ def get_confirm(event, context):
237279

238280
def get(event, contest):
239281
items = ddb.scan(TableName=event["RegistrantsTable"])
282+
pairs = set([(item["teamId"]["N"], item["display_name"]["S"]) for item in items.get("Items", [])])
240283
return [{
241-
"team_id": item["teamId"]["N"],
242-
"team_name": item["username"]["S"]
243-
} for item in items.get("Items", [])]
284+
"team_id": t[0],
285+
"team_name": t[1]
286+
} for t in pairs]
244287

245288

246289
def lambda_handler(event, context):

cloudformation.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ Parameters:
7070
HMACSecret:
7171
Description: String that is used as the secret key for the HMAC to validate emails during registration.
7272
Type: String
73+
RegistrationMode:
74+
Description: Determines whether to permit registration from emails that aren't already in the DB.
75+
This does not exempt registrants from confirming their email address, as we still need to know that
76+
we can contact them at that address.
77+
Type: String
78+
AllowedValues:
79+
- Open
80+
- Closed
7381

7482
Conditions:
7583
TallyCodePlaceholder:
@@ -438,8 +446,10 @@ Resources:
438446
Variables:
439447
XraySampleRate: !Ref XraySampleRate
440448
SCORECARD_LOG_EVENTS: "TRUE"
449+
SES_EMAIL_SOURCE: !Ref SESEmailSource
441450
SES_REGION: !Ref SESRegion
442451
HMAC_SECRET: !Ref HMACSecret
452+
REGISTRATION_MODE: !Ref RegistrationMode
443453
API_URL: !Sub https://${API}.execute-api.${AWS::Region}.amazonaws.com/Main
444454
Runtime: python3.6
445455
Handler: Register.lambda_handler

deploy.py

+19-3
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ def main():
9292
help=
9393
"""A list of tags as a dict to use as tag keys and values for the CloudFormation stack."""
9494
)
95+
parser.add_argument(
96+
"--registration-mode",
97+
required=False,
98+
default="Open",
99+
type=lambda v: {k:k for k in ["Open", "Closed"]}[v],
100+
help="""
101+
Determines whether to permit registration from emails that aren't already in the DB.
102+
This does not exempt registrants from confirming their email address, as we still need to
103+
know that we can contact them at that address."""
104+
)
95105
pargs = parser.parse_args()
96106

97107
if pargs.registration_email_source is not None and pargs.hmac_secret is None:
@@ -110,17 +120,17 @@ def main():
110120
exit(2)
111121

112122
print("Building code zip files for deployment...")
113-
tally_code = (str(uuid.uuid1()),
123+
tally_code = (str(uuid.uuid4()),
114124
zip_files([
115125
"ScoreCardTally.py", "S3KeyValueStore.py",
116126
"XrayChain.py", "util.py"
117127
]))
118-
submit_code = (str(uuid.uuid1()),
128+
submit_code = (str(uuid.uuid4()),
119129
zip_files([
120130
"ScoreCardSubmit.py", "S3KeyValueStore.py",
121131
"XrayChain.py", "util.py"
122132
]))
123-
register_code = (str(uuid.uuid1()),
133+
register_code = (str(uuid.uuid4()),
124134
zip_files(["Register.py", "XrayChain.py", "util.py"]))
125135

126136
print("Uploading code zip files to S3 bucket (%s)..." % pargs.code_bucket)
@@ -151,6 +161,12 @@ def main():
151161
"ParameterValue": pargs.registration_email_source
152162
})
153163

164+
if pargs.registration_mode is not None:
165+
stack_params.append({
166+
"ParameterKey": "RegistrationMode",
167+
"ParameterValue": pargs.registration_mode
168+
})
169+
154170
if pargs.hmac_secret is not None:
155171
stack_params.append({
156172
"ParameterKey": "HMACSecret",

webassets/register.html

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@
7575
<div ng-show="submission_status == 'failed_duplicate'">
7676
<label style="float:center;color:darkorange">Username already exists.</label>
7777
</div>
78+
<div ng-show="submission_status == 'failed_unknown'">
79+
<label style="float:center;color:darkmagenta">Unknown error encountered.</label>
80+
</div>
7881
</div>
7982
</div>
8083
</body>

0 commit comments

Comments
 (0)