Skip to content

Commit 77ba50c

Browse files
author
Mike Grima
committed
Added some niceties around AWS Config
- Added a JSON unwrapping nicety for AWS Config querying convenience.
1 parent 3428942 commit 77ba50c

File tree

4 files changed

+221
-0
lines changed

4 files changed

+221
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -201,5 +201,8 @@ samconfig.toml
201201
# VS Code:
202202
.vscode/
203203

204+
# Some kubernetes things
205+
.kuberlr/
206+
204207
# By default ignore configuration file changes. If this needs to be changed, you need to force add in git:
205208
src/starfleet/configuration_files/

ATTRIBUTIONS

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
THIS FILE CONTAINS OTHER ATTRIBUTIONS FOR THIS OSS PROJECT:
2+
3+
1. Spaceship Icons:
4+
- The spaceship icons are from the *VERY COOL* The Ur-Quan Masters project: https://sc2.sourceforge.net/
5+
- The Starfleet open source project has no relation to The Ur-Quan Masters project
6+
- The icons were scaled up for size
7+
- The content -- … graphics, … -- are copyright (C) 1992, 1993, 2002 Toys for Bob, Inc. or their respective creators.
8+
The content may be used freely under the terms of the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 license (available at http://creativecommons.org/licenses/by-nc-sa/2.5/).
9+
- The icons are distributed with no warranties of any kind
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""A general set of niceties that Starfleet workers can use to do things that are nice.
2+
3+
This mostly defines some shortcut code utilities that workers can use for a variety of use cases.
4+
5+
:Module: starfleet.worker_ships.niceties
6+
:Copyright: (c) 2023 by Gemini Trust Company, LLC., see AUTHORS for more info
7+
:License: See the LICENSE file for details
8+
:Author: Mike Grima <[email protected]>
9+
"""
10+
import datetime
11+
import json
12+
from typing import Any
13+
from urllib.parse import unquote_plus
14+
15+
16+
def un_wrap_json(json_obj: Any) -> Any:
17+
"""Helper function to unwrap nested JSON in the AWS Config resource configuration."""
18+
# pylint: disable=C0103,W0703,R0911
19+
# Is this a field that we can safely return?
20+
if isinstance(json_obj, (type(None), int, bool, float)): # noqa
21+
return json_obj
22+
23+
# Is this a Datetime? Convert it to a string and return it:
24+
if isinstance(json_obj, datetime.datetime):
25+
return str(json_obj)
26+
27+
# Is this a Dictionary?
28+
if isinstance(json_obj, dict):
29+
decoded = {}
30+
for k, v in json_obj.items():
31+
decoded[k] = un_wrap_json(v)
32+
33+
# Is this a List?
34+
elif isinstance(json_obj, list):
35+
decoded = []
36+
for x in json_obj:
37+
decoded.append(un_wrap_json(x))
38+
39+
# Yes, try to sort the contents of lists. This is because AWS does not consistently store list ordering for many resource types:
40+
try:
41+
sorted_list = sorted(decoded)
42+
decoded = sorted_list
43+
except Exception: # noqa # nosec # If we can't sort then NBD
44+
pass
45+
else:
46+
# Try to load the JSON string:
47+
try:
48+
# Check if the string starts with a "[" or a "{" (because apparently '123' is a valid JSON 😒😒😒)
49+
for check_field in ["{", "[", '"{', '"[']: # Some of the double-wrapping is really ridiculous 😒
50+
if json_obj.startswith(check_field):
51+
decoded = json.loads(json_obj)
52+
53+
# If we loaded this properly, then we need to pass the decoded JSON back in for all the nested stuff:
54+
return un_wrap_json(decoded)
55+
56+
# Check if this string is URL Encoded - if it is, then re-run it through:
57+
decoded = unquote_plus(json_obj)
58+
if decoded != json_obj:
59+
return un_wrap_json(decoded)
60+
61+
return json_obj
62+
63+
# If we didn't get a JSON back (exception), then just return the raw value back:
64+
except Exception: # noqa
65+
return json_obj
66+
67+
return decoded
+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
"""Tests the worker ship niceties.
2+
3+
:Module: starfleet.tests.worker_ship_utils.test_niceties
4+
:Copyright: (c) 2023 by Gemini Trust Company, LLC., see AUTHORS for more info
5+
:License: See the LICENSE file for details
6+
:Author: Mike Grima <[email protected]>
7+
"""
8+
# pylint: disable=unused-argument
9+
import datetime
10+
11+
12+
def test_unwrap_json() -> None:
13+
"""This tests the unwrapping of AWS Config json."""
14+
from starfleet.worker_ships.niceties import un_wrap_json
15+
16+
# Simple AWS policy JSON:
17+
test_str = (
18+
'{"policyText": "{\\"Version\\":\\"2008-10-17\\",\\"Statement\\":[{\\"Sid\\":\\"AccountReadOnly\\",\\"Effect\\":\\"Allow\\",\\"Principal\\":{'
19+
'\\"AWS\\":[\\"arn:aws:iam::000000000001:root\\"]},\\"Action\\":[\\"s3:Get*\\",\\"s3:List*\\"],\\"Resource\\":['
20+
'\\"arn:aws:s3:::some-bucket\\",\\"arn:aws:s3:::some-bucket/*\\"]}]}"} '
21+
)
22+
should_equal = {
23+
"policyText": {
24+
"Version": "2008-10-17",
25+
"Statement": [
26+
{
27+
"Sid": "AccountReadOnly",
28+
"Effect": "Allow",
29+
"Principal": {"AWS": ["arn:aws:iam::000000000001:root"]},
30+
"Action": ["s3:Get*", "s3:List*"],
31+
"Resource": ["arn:aws:s3:::some-bucket", "arn:aws:s3:::some-bucket/*"],
32+
}
33+
],
34+
}
35+
}
36+
assert un_wrap_json(test_str) == should_equal
37+
38+
# With URL encoding:
39+
test_str_with_url_encoding = (
40+
'{"path":"/","roleName":"SomeRole","roleId":"AROAALSKDJFLAKSDJFKLJSDF",'
41+
'"arn":"arn:aws:iam::000000000001:role/SomeRole","createDate":"2023-11-12T18:41:33.000Z",'
42+
'"assumeRolePolicyDocument":"%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C'
43+
'%22Principal%22%3A%7B%22Service%22%3A%22ec2.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D",'
44+
'"instanceProfileList":[{"path":"/","instanceProfileName":"SomeRole",'
45+
'"instanceProfileId":"AROAALSKDJFLAKSDJFKLJSDF",'
46+
'"arn":"arn:aws:iam::000000000001:instance-profile/SomeRole","createDate":"2023-11-12T18:41:33.000Z",'
47+
'"roles":[{"path":"/","roleName":"SomeRole","roleId":"AROAALSKDJFLAKSDJFKLJSDF",'
48+
'"arn":"arn:aws:iam::000000000001:role/SomeRole","createDate":"2023-11-12T18:41:33.000Z",'
49+
'"assumeRolePolicyDocument":"%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C'
50+
'%22Principal%22%3A%7B%22Service%22%3A%22ec2.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D",'
51+
'"description":null,"maxSessionDuration":null,"permissionsBoundary":null,"tags":[],"roleLastUsed":null}]}],'
52+
'"rolePolicyList":[{"policyName":"Config",'
53+
'"policyDocument":"%7B%22Statement%22%3A%5B%7B%22Action%22%3A%5B%22config%3Aselectaggregateresourceconfig%22%5D%2C'
54+
'%22Effect%22%3A%22Allow%22%2C%22Resource%22%3A%5B%22%2A%22%5D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D"}],'
55+
'"attachedManagedPolicies":[{"policyName":"SomePolicy",'
56+
'"policyArn":"arn:aws:iam::000000000001:policy/SomePolicy"}],"permissionsBoundary":null,"tags":[],'
57+
'"roleLastUsed":null} '
58+
)
59+
should_equal = {
60+
"path": "/",
61+
"roleName": "SomeRole",
62+
"roleId": "AROAALSKDJFLAKSDJFKLJSDF",
63+
"arn": "arn:aws:iam::000000000001:role/SomeRole",
64+
"createDate": "2023-11-12T18:41:33.000Z",
65+
"assumeRolePolicyDocument": {
66+
"Version": "2012-10-17",
67+
"Statement": [{"Effect": "Allow", "Principal": {"Service": "ec2.amazonaws.com"}, "Action": "sts:AssumeRole"}],
68+
},
69+
"instanceProfileList": [
70+
{
71+
"path": "/",
72+
"instanceProfileName": "SomeRole",
73+
"instanceProfileId": "AROAALSKDJFLAKSDJFKLJSDF",
74+
"arn": "arn:aws:iam::000000000001:instance-profile/SomeRole",
75+
"createDate": "2023-11-12T18:41:33.000Z",
76+
"roles": [
77+
{
78+
"path": "/",
79+
"roleName": "SomeRole",
80+
"roleId": "AROAALSKDJFLAKSDJFKLJSDF",
81+
"arn": "arn:aws:iam::000000000001:role/SomeRole",
82+
"createDate": "2023-11-12T18:41:33.000Z",
83+
"assumeRolePolicyDocument": {
84+
"Version": "2012-10-17",
85+
"Statement": [{"Effect": "Allow", "Principal": {"Service": "ec2.amazonaws.com"}, "Action": "sts:AssumeRole"}],
86+
},
87+
"description": None,
88+
"maxSessionDuration": None,
89+
"permissionsBoundary": None,
90+
"tags": [],
91+
"roleLastUsed": None,
92+
}
93+
],
94+
}
95+
],
96+
"rolePolicyList": [
97+
{
98+
"policyName": "Config",
99+
"policyDocument": {
100+
"Statement": [{"Action": ["config:selectaggregateresourceconfig"], "Effect": "Allow", "Resource": ["*"]}],
101+
"Version": "2012-10-17",
102+
},
103+
}
104+
],
105+
"attachedManagedPolicies": [{"policyName": "SomePolicy", "policyArn": "arn:aws:iam::000000000001:policy/SomePolicy"}],
106+
"permissionsBoundary": None,
107+
"tags": [],
108+
"roleLastUsed": None,
109+
}
110+
assert un_wrap_json(test_str_with_url_encoding) == should_equal
111+
112+
# And again with some strange nesting:
113+
test_nested = {
114+
"how": [
115+
"nested",
116+
{
117+
"can": '{"we": "really", "really": "[\\"get\\", \\"{\\\\\\"into\\\\\\": \\\\\\"{\\\\\\\\\\\\\\"really\\\\\\\\\\\\\\": '
118+
'\\\\\\\\\\\\\\"deep\\\\\\\\\\\\\\"}\\\\\\"}\\"]"}'
119+
},
120+
94,
121+
3.14,
122+
{"more": '{"and again": "{\\"this and\\": \\"that\\"}"}'},
123+
]
124+
}
125+
should_equal = {
126+
"how": ["nested", {"can": {"we": "really", "really": ["get", {"into": {"really": "deep"}}]}}, 94, 3.14, {"more": {"and again": {"this and": "that"}}}]
127+
}
128+
assert un_wrap_json(test_nested) == should_equal
129+
130+
# And values that are non-JSON:
131+
now = datetime.datetime.utcnow()
132+
assert un_wrap_json(now) == str(now)
133+
assert un_wrap_json(19) == 19
134+
assert un_wrap_json(3.14) == 3.14
135+
assert un_wrap_json(True) is True
136+
137+
# Try it with something bizarre, like a function:
138+
assert un_wrap_json(test_unwrap_json) == test_unwrap_json # pylint: disable=comparison-with-callable
139+
140+
# Test that we are sorting lists:
141+
test_sorted = {"a_list": [11, 5.2, 2, 0, 3], "b_list": ["a", "b", "c"], "with_nested_objs": [{"A": "Value"}, {"qwerty": "uiop[]"}]}
142+
assert un_wrap_json(test_sorted) == {"a_list": [0, 2, 3, 5.2, 11], "b_list": ["a", "b", "c"], "with_nested_objs": [{"A": "Value"}, {"qwerty": "uiop[]"}]}

0 commit comments

Comments
 (0)