From 77e59630eb2b125ea9975213f99ce1fc6d8958e5 Mon Sep 17 00:00:00 2001 From: hyeon Date: Tue, 13 Aug 2024 16:33:50 +0900 Subject: [PATCH 01/11] Add derive function into Address model --- common/lib9c/models/address.py | 12 ++++++++++++ tests/lib9c/models/test_address.py | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/common/lib9c/models/address.py b/common/lib9c/models/address.py index c8086946..dcf522dd 100644 --- a/common/lib9c/models/address.py +++ b/common/lib9c/models/address.py @@ -1,5 +1,8 @@ from __future__ import annotations +import hashlib +import hmac + class Address: def __init__(self, addr: str): @@ -20,5 +23,14 @@ def long_format(self): def short_format(self): return self.raw.hex() + def derive(self, key: str) -> Address: + return Address( + hmac.new( + key.encode("utf-8"), + self.raw, + digestmod=hashlib.sha1 + ).hexdigest() + ) + def __eq__(self, other: Address): return self.raw == other.raw diff --git a/tests/lib9c/models/test_address.py b/tests/lib9c/models/test_address.py index dd247ed8..606219b0 100644 --- a/tests/lib9c/models/test_address.py +++ b/tests/lib9c/models/test_address.py @@ -30,3 +30,12 @@ def test_address(addr): def test_address_error(addr): with pytest.raises(ValueError) as e: Address(addr) + + +@pytest.mark.parametrize(("key", "result"), [ + ("key1", "0x518c0044a81c7d3747Ad416Df7227e53233F2e10"), + ("key2", "0xEb5cC07Eb104B082C1ae82E018340e989700ca31") +]) +def test_address_derive(key, result): + addr = Address("0xa5f7e0bd63AD2749D66380f36Eb33Fe0ba50A27D") + assert addr.derive(key) == Address(result) From 314a37017e18ed4e5d785ecb9f58026f9564ea72 Mon Sep 17 00:00:00 2001 From: hyeon Date: Tue, 13 Aug 2024 16:39:54 +0900 Subject: [PATCH 02/11] Introduce IssueTokensFromGarage action --- .../lib9c/actions/issue_tokens_from_garage.py | 53 ++++++++ common/lib9c/exceptions.py | 2 + .../actions/test_issue_tokens_from_garage.py | 120 ++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 common/lib9c/actions/issue_tokens_from_garage.py create mode 100644 common/lib9c/exceptions.py create mode 100644 tests/lib9c/actions/test_issue_tokens_from_garage.py diff --git a/common/lib9c/actions/issue_tokens_from_garage.py b/common/lib9c/actions/issue_tokens_from_garage.py new file mode 100644 index 00000000..88fc112e --- /dev/null +++ b/common/lib9c/actions/issue_tokens_from_garage.py @@ -0,0 +1,53 @@ +from typing import List, Optional + +from common.lib9c.actions import ActionBase +from lib9c.models.fungible_asset_value import FungibleAssetValue + + +class FavIssueSpec: + def __init__(self, fav: FungibleAssetValue): + self._fav = fav + + @property + def plain_value(self): + return \ + [ + self._fav.plain_value, + None # No Item spec + ] + + +class ItemIssueSpec: + def __init__(self, fungible_item_id: [str | bytes], amount: int): + self.item_id = fungible_item_id if isinstance(fungible_item_id, bytes) else bytes.fromhex(fungible_item_id) + self.amount = amount + + @property + def plain_value(self): + return \ + [ + None, # No FAV spec + [ + self.item_id, + self.amount + ] + ] + + +class IssueTokensFromGarage(ActionBase): + """ + Python port of `IssueTokensFromGarage` action from lib9c + + - type_id: `issue_tokens_from_garage` + - values: List[spec] + - spec: List[FavIssueSpec | ItemIssueSpec] + """ + TYPE_ID: str = "issue_tokens_from_garage" + + def __init__(self, *, values: List[FavIssueSpec | ItemIssueSpec], _id: Optional[str] = None): + super().__init__(self.TYPE_ID, _id) + self._values: List[FavIssueSpec | ItemIssueSpec] = values + + @property + def _plain_value(self): + return [x.plain_value for x in self._values] if self._values else None diff --git a/common/lib9c/exceptions.py b/common/lib9c/exceptions.py new file mode 100644 index 00000000..c27e5c0f --- /dev/null +++ b/common/lib9c/exceptions.py @@ -0,0 +1,2 @@ +class InvalidAmountException(Exception): + pass diff --git a/tests/lib9c/actions/test_issue_tokens_from_garage.py b/tests/lib9c/actions/test_issue_tokens_from_garage.py new file mode 100644 index 00000000..8fdf1e3c --- /dev/null +++ b/tests/lib9c/actions/test_issue_tokens_from_garage.py @@ -0,0 +1,120 @@ +from decimal import Decimal + +import pytest + +from common.lib9c.actions.issue_tokens_from_garage import IssueTokensFromGarage, FavIssueSpec, ItemIssueSpec +from common.lib9c.models.currency import Currency +from common.lib9c.models.fungible_asset_value import FungibleAssetValue + +TEST_ID = "0d0d9e0cbc1b11eeb0dc6fd71476142a" +TEST_FUNGIBLE_ITEM_ID = "f8faf92c9c0d0e8e06694361ea87bfc8b29a8ae8de93044b98470a57636ed0e0" # Golden Dust +TEST_FUNGIBLE_ITEM_BINARY = b'\xf8\xfa\xf9,\x9c\r\x0e\x8e\x06iCa\xea\x87\xbf\xc8\xb2\x9a\x8a\xe8\xde\x93\x04K\x98G\nWcn\xd0\xe0' +ACTION_TEST_DATA = [ + ( + { + "_id": TEST_ID, + "values": [ + FavIssueSpec(FungibleAssetValue.from_raw_data("Crystal", 18, amount=Decimal("1000"))) + ] + }, + [ + [ + [ + { + "ticker": "Crystal", + "decimalPlaces": b'\x12', + "minters": None, + }, + 1000 * 10 ** 18 + ], + None + ], + ] + ), + ( + { + "_id": TEST_ID, + "values": [ + ItemIssueSpec(TEST_FUNGIBLE_ITEM_ID, 42) + ] + }, + [ + [ + None, + [ + TEST_FUNGIBLE_ITEM_BINARY, + 42 + ] + ] + ] + ), + ( + { + "_id": TEST_ID, + "values": [ + ItemIssueSpec("f8faf92c9c0d0e8e06694361ea87bfc8b29a8ae8de93044b98470a57636ed0e0", 42) + ] + }, + [ + [ + None, + [ + b'\xf8\xfa\xf9,\x9c\r\x0e\x8e\x06iCa\xea\x87\xbf\xc8\xb2\x9a\x8a\xe8\xde\x93\x04K\x98G\nWcn\xd0\xe0', + 42 + ] + ] + ] + ), + ( + { + "_id": TEST_ID, + "values": [ + FavIssueSpec(FungibleAssetValue.from_raw_data("Crystal", 18, amount=Decimal("1000"))), + ItemIssueSpec("f8faf92c9c0d0e8e06694361ea87bfc8b29a8ae8de93044b98470a57636ed0e0", 42) + ] + }, + [ + [ + [ + { + "ticker": "Crystal", + "decimalPlaces": b'\x12', + "minters": None, + }, + 1000 * 10 ** 18 + ], + None + ], + [ + None, + [ + b'\xf8\xfa\xf9,\x9c\r\x0e\x8e\x06iCa\xea\x87\xbf\xc8\xb2\x9a\x8a\xe8\xde\x93\x04K\x98G\nWcn\xd0\xe0', + 42 + ] + ] + ] + ) +] + + +def test_fav_issue_spec(): + fav = FungibleAssetValue(Currency("Crystal", 18), Decimal(42)) + fav_issue_spec = FavIssueSpec(fav) + assert fav_issue_spec.plain_value[0] == fav.plain_value + + +@pytest.mark.parametrize("data", [TEST_FUNGIBLE_ITEM_ID, TEST_FUNGIBLE_ITEM_BINARY]) +def test_item_issue_spec(data): + item_issue_spec = ItemIssueSpec(data, 42) + assert item_issue_spec.plain_value == [None, [TEST_FUNGIBLE_ITEM_BINARY, 42]] + + +@pytest.mark.parametrize("test_data", ACTION_TEST_DATA) +def test_action(test_data): + data, expected = test_data + action = IssueTokensFromGarage(**data) + plain_value = action.plain_value + values = plain_value["values"] + + assert plain_value["type_id"] == "issue_tokens_from_garage" + assert values == expected From 4658f292bf8dd7947247f5ac31edca3277fe2679 Mon Sep 17 00:00:00 2001 From: hyeon Date: Wed, 14 Aug 2024 13:24:19 +0900 Subject: [PATCH 03/11] Create token issuing lambda --- common/consts.py | 11 +++++++ worker/worker/issue_tokens.py | 59 +++++++++++++++++++++++++++++++++++ worker/worker_cdk_stack.py | 15 +++++++++ 3 files changed, 85 insertions(+) create mode 100644 worker/worker/issue_tokens.py diff --git a/common/consts.py b/common/consts.py index bc6e49cd..a7465439 100644 --- a/common/consts.py +++ b/common/consts.py @@ -29,3 +29,14 @@ "SOULSTONE_1003", # Valkyrie of Light "SOULSTONE_1004", # Lil' Fenrir ) + +ITEM_FUNGIBLE_ID_DICT = { + "400000": "3991e04dd808dc0bc24b21f5adb7bf1997312f8700daf1334bf34936e8a0813a", + "500000": "00dfffe23964af9b284d121dae476571b7836b8d9e2e5f510d92a840fecc64fe", + "600201": "f8faf92c9c0d0e8e06694361ea87bfc8b29a8ae8de93044b98470a57636ed0e0", + "600202": "08f566bb43570aad34c1790901f824dd5609db880afebd5382fcec054203d92a", + "600203": "", + "800201": "1a755098a2bc0659a063107df62e2ff9b3cdaba34d96b79519f504b996f53820", + "CRYSTAL": "FAV__CRYSTAL", + "RUNE_GOLDENLEAF": "FAV__RUNE_GOLDENLEAF", +} diff --git a/worker/worker/issue_tokens.py b/worker/worker/issue_tokens.py new file mode 100644 index 00000000..c2b41d8d --- /dev/null +++ b/worker/worker/issue_tokens.py @@ -0,0 +1,59 @@ +# This worker manually issues token from input data which in following format: +# List[Tuple[{ticker:str}, {decimal_places:int}, {fungible_id:str}, {item_id:int}, {amount:int}]] +# Use `ticker`, `decimal_places` and `amount` for FAV +# minters of the FAV is always assumed as None. +# Use `fungible_id` or `item_id` and `amount` for item. +# If both fungible_id and item_id are provided, the worker uses fungible_id. +import datetime +import os +from decimal import Decimal + +from common._crypto import Account +from common._graphql import GQL +from common.consts import ITEM_FUNGIBLE_ID_DICT +from common.lib9c.actions.issue_tokens_from_garage import FavIssueSpec, ItemIssueSpec, IssueTokensFromGarage +from common.lib9c.models.fungible_asset_value import FungibleAssetValue +from common.utils.aws import fetch_parameter, fetch_kms_key_id +from common.utils.receipt import PlanetID +from common.utils.transaction import create_unsigned_tx, append_signature_to_unsigned_tx + +# NOTE: Use this lambda function by executing manually in AWS console. + +# Set these values by manual form here +NONCE = 0 +PLANET_ID = PlanetID.XXX +GQL_URL = "https://example.com/graphql" # Use Odin/Heimdall GQL host +# to here + +HEADLESS_GQL_JWT_SECRET = fetch_parameter( + os.environ.get("REGION_NAME"), + f"{os.environ.get('STAGE')}_9c_IAP_HEADLESS_GQL_JWT_SECRET", + True +)["Value"] +DICT_HEADER = {"ticker", "decimal_places", "fungible_id", "item_id", "amount"} + + +def issue(event, context): + spec_list = [] + gql = GQL(GQL_URL, HEADLESS_GQL_JWT_SECRET) + account = Account(fetch_kms_key_id(os.environ.get("STAGE"), os.environ.get("REGION_NAME"))) + + for data in event: + data = dict(zip(DICT_HEADER, data)) + if data["ticker"]: + spec_list.append(FavIssueSpec(FungibleAssetValue.from_raw_data( + ticker=data["ticker"], decimal_places=int(data["decimal_places"]), minters=None, + amount=Decimal(data["amount"])) + )) + else: + fungible_id = data["fungible_id"] if data["fungible_id"] else ITEM_FUNGIBLE_ID_DICT[data["item_id"]] + spec_list.append(ItemIssueSpec(fungible_item_id=fungible_id, amount=int(data["amount"]))) + + action = IssueTokensFromGarage(values=spec_list) + utx = create_unsigned_tx( + planet_id=PLANET_ID, public_key=account.pubkey.hex(), address=account.address, nonce=NONCE, + plain_value=action.plain_value, timestamp=datetime.datetime.utcnow() + datetime.timedelta(days=1) + ) + signature = account.sign_tx(utx) + signed_tx = append_signature_to_unsigned_tx(utx, signature) + return gql.stage(signed_tx), NONCE, signed_tx diff --git a/worker/worker_cdk_stack.py b/worker/worker_cdk_stack.py index 1d4beedf..7b20bf23 100644 --- a/worker/worker_cdk_stack.py +++ b/worker/worker_cdk_stack.py @@ -278,6 +278,21 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: memory_size=512, ) + token_issuer = _lambda.Function( + self, f"{config.stage}-9c-iap-token-issue-function", + function_name=f"{config.stage}-9c-iap-issue-token", + runtime=_lambda.Runtime.PYTHON_3_10, + description=f"Execute IssueTokensFromGarage action", + code=_lambda.AssetCode("worker/worker", exclude=exclude_list), + handler="issue_tokens.issue", + layers=[layer], + role=role, + vpc=shared_stack.vpc, + timeout=cdk_core.Duration.seconds(300), # 5min + environment=env, + memory_size=256, + ) + lambda_warmer = _lambda.Function( self, f"{config.stage}-9c-iap-lambda-warmer", function_name=f"{config.stage}-9c-iap-lambda-warmer", From b153cf7e305996d1fc1cbfd85f459a132333f664 Mon Sep 17 00:00:00 2001 From: hyeon Date: Wed, 14 Aug 2024 13:30:11 +0900 Subject: [PATCH 04/11] Remove unused lambda --- worker/worker_cdk_stack.py | 80 +++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/worker/worker_cdk_stack.py b/worker/worker_cdk_stack.py index 7b20bf23..849ca8f5 100644 --- a/worker/worker_cdk_stack.py +++ b/worker/worker_cdk_stack.py @@ -179,8 +179,9 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: if config.stage == "mainnet": ten_minute_event_rule.add_target(_event_targets.LambdaFunction(status_monitor)) - else: - hourly_event_rule.add_target(_event_targets.LambdaFunction(status_monitor)) + # If you want to test monitor in internal, uncomment following statement + # else: + # hourly_event_rule.add_target(_event_targets.LambdaFunction(status_monitor)) # IAP Voucher voucher_env = deepcopy(env) @@ -222,43 +223,44 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: ) everyday_0100_rule.add_target(_event_targets.LambdaFunction(google_refund_handler)) - # Golden dust by NCG handler - env["GOLDEN_DUST_REQUEST_SHEET_ID"] = config.golden_dust_request_sheet_id - env["GOLDEN_DUST_WORK_SHEET_ID"] = config.golden_dust_work_sheet_id - env["FORM_SHEET"] = config.form_sheet - gd_handler = _lambda.Function( - self, f"{config.stage}-9c-iap-goldendust-handler-function", - function_name=f"{config.stage}-9c-iap-goldendust-handler", - runtime=_lambda.Runtime.PYTHON_3_10, - description="Request handler for Golden dust by NCG for PC users", - code=_lambda.AssetCode("worker/worker", exclude=exclude_list), - handler="golden_dust_by_ncg.handle_request", - layers=[layer], - role=role, - vpc=shared_stack.vpc, - timeout=cdk_core.Duration.minutes(8), - environment=env, - memory_size=512, - reserved_concurrent_executions=1, - ) - ten_minute_event_rule.add_target(_event_targets.LambdaFunction(gd_handler)) - - # Golden dust unload Tx. tracker - gd_tracker = _lambda.Function( - self, f"{config.stage}-9c-iap-goldendust-tracker-function", - function_name=f"{config.stage}-9c-iap-goldendust-tracker", - runtime=_lambda.Runtime.PYTHON_3_10, - description=f"Tx. status tracker for golden dust unload for PC users", - code=_lambda.AssetCode("worker/worker", exclude=exclude_list), - handler="golden_dust_by_ncg.track_tx", - layers=[layer], - role=role, - vpc=shared_stack.vpc, - timeout=cdk_core.Duration.seconds(50), - environment=env, - memory_size=256, - ) - minute_event_rule.add_target(_event_targets.LambdaFunction(gd_tracker)) + # Event finished + # # Golden dust by NCG handler + # env["GOLDEN_DUST_REQUEST_SHEET_ID"] = config.golden_dust_request_sheet_id + # env["GOLDEN_DUST_WORK_SHEET_ID"] = config.golden_dust_work_sheet_id + # env["FORM_SHEET"] = config.form_sheet + # gd_handler = _lambda.Function( + # self, f"{config.stage}-9c-iap-goldendust-handler-function", + # function_name=f"{config.stage}-9c-iap-goldendust-handler", + # runtime=_lambda.Runtime.PYTHON_3_10, + # description="Request handler for Golden dust by NCG for PC users", + # code=_lambda.AssetCode("worker/worker", exclude=exclude_list), + # handler="golden_dust_by_ncg.handle_request", + # layers=[layer], + # role=role, + # vpc=shared_stack.vpc, + # timeout=cdk_core.Duration.minutes(8), + # environment=env, + # memory_size=512, + # reserved_concurrent_executions=1, + # ) + # ten_minute_event_rule.add_target(_event_targets.LambdaFunction(gd_handler)) + # + # # Golden dust unload Tx. tracker + # gd_tracker = _lambda.Function( + # self, f"{config.stage}-9c-iap-goldendust-tracker-function", + # function_name=f"{config.stage}-9c-iap-goldendust-tracker", + # runtime=_lambda.Runtime.PYTHON_3_10, + # description=f"Tx. status tracker for golden dust unload for PC users", + # code=_lambda.AssetCode("worker/worker", exclude=exclude_list), + # handler="golden_dust_by_ncg.track_tx", + # layers=[layer], + # role=role, + # vpc=shared_stack.vpc, + # timeout=cdk_core.Duration.seconds(50), + # environment=env, + # memory_size=256, + # ) + # minute_event_rule.add_target(_event_targets.LambdaFunction(gd_tracker)) # Manual unload function # This function does not have trigger. Go to AWS console and run manually. From 2104fdfbe716945ed8e3f3de2299c4150b58818a Mon Sep 17 00:00:00 2001 From: hyeon Date: Wed, 14 Aug 2024 13:56:59 +0900 Subject: [PATCH 05/11] Need common path to load common library --- common/lib9c/actions/issue_tokens_from_garage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib9c/actions/issue_tokens_from_garage.py b/common/lib9c/actions/issue_tokens_from_garage.py index 88fc112e..56042c6b 100644 --- a/common/lib9c/actions/issue_tokens_from_garage.py +++ b/common/lib9c/actions/issue_tokens_from_garage.py @@ -1,7 +1,7 @@ from typing import List, Optional from common.lib9c.actions import ActionBase -from lib9c.models.fungible_asset_value import FungibleAssetValue +from common.lib9c.models.fungible_asset_value import FungibleAssetValue class FavIssueSpec: From 0be54d70d108dcc0dce907cb5237a916734a0080 Mon Sep 17 00:00:00 2001 From: hyeon Date: Mon, 19 Aug 2024 13:39:21 +0900 Subject: [PATCH 06/11] Merge derive address functions - Deprecate old function - Only one usage: need to be changed - Change new function - Use encode_checksum logic instead of hexdigest --- common/_crypto.py | 2 ++ common/lib9c/models/address.py | 38 ++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/common/_crypto.py b/common/_crypto.py index 4e336061..20552a4e 100644 --- a/common/_crypto.py +++ b/common/_crypto.py @@ -144,6 +144,8 @@ def sign_tx(self, unsigned_tx: bytes) -> bytes: return der_encode(seq) + +# Deprecated def derive_address(address: Union[str, bytes], key: Union[str, bytes], get_byte: bool = False) -> Union[bytes, str]: """ Derive given address using key. diff --git a/common/lib9c/models/address.py b/common/lib9c/models/address.py index dcf522dd..377a6a20 100644 --- a/common/lib9c/models/address.py +++ b/common/lib9c/models/address.py @@ -3,6 +3,8 @@ import hashlib import hmac +import eth_utils + class Address: def __init__(self, addr: str): @@ -25,12 +27,44 @@ def short_format(self): def derive(self, key: str) -> Address: return Address( - hmac.new( + self.__checksum_encode(hmac.new( key.encode("utf-8"), self.raw, digestmod=hashlib.sha1 - ).hexdigest() + ).digest()) ) + def __checksum_encode(self, addr: bytes) -> str: # Takes a 20-byte binary address as input + """ + Convert input address to checksum encoded address without prefix "0x" + See [ERC-55](https://eips.ethereum.org/EIPS/eip-55) + + :param addr: 20-bytes binary address + :return: checksum encoded address as string + """ + hex_addr = addr.hex() + checksum_buffer = "" + + # Treat the hex address as ascii/utf-8 for keccak256 hashing + hashed_address = eth_utils.keccak(text=hex_addr).hex() + + # Iterate over each character in the hex address + for nibble_index, character in enumerate(hex_addr): + if character in "0123456789": + # We can't upper-case the decimal digits + checksum_buffer += character + elif character in "abcdef": + # Check if the corresponding hex digit (nibble) in the hash is 8 or higher + hashed_address_nibble = int(hashed_address[nibble_index], 16) + if hashed_address_nibble > 7: + checksum_buffer += character.upper() + else: + checksum_buffer += character + else: + raise eth_utils.ValidationError( + f"Unrecognized hex character {character!r} at position {nibble_index}" + ) + return checksum_buffer + def __eq__(self, other: Address): return self.raw == other.raw From c88e906ad42a077127ee060f1c63efb1a5afae12 Mon Sep 17 00:00:00 2001 From: hyeon Date: Mon, 19 Aug 2024 13:42:31 +0900 Subject: [PATCH 07/11] Fix issue_token script - keys are not set - Update return values --- worker/worker/issue_tokens.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/worker/worker/issue_tokens.py b/worker/worker/issue_tokens.py index c2b41d8d..2fc680d4 100644 --- a/worker/worker/issue_tokens.py +++ b/worker/worker/issue_tokens.py @@ -8,6 +8,7 @@ import os from decimal import Decimal +from common import logger from common._crypto import Account from common._graphql import GQL from common.consts import ITEM_FUNGIBLE_ID_DICT @@ -30,7 +31,7 @@ f"{os.environ.get('STAGE')}_9c_IAP_HEADLESS_GQL_JWT_SECRET", True )["Value"] -DICT_HEADER = {"ticker", "decimal_places", "fungible_id", "item_id", "amount"} +DICT_HEADER = ("ticker", "decimal_places", "fungible_id", "item_id", "amount") def issue(event, context): @@ -56,4 +57,8 @@ def issue(event, context): ) signature = account.sign_tx(utx) signed_tx = append_signature_to_unsigned_tx(utx, signature) - return gql.stage(signed_tx), NONCE, signed_tx + success, message, tx_id = gql.stage(signed_tx) + + logger.info(f"{success}::'{message}'::{tx_id} with nonce {NONCE}") + logger.debug(signed_tx.hex()) + return success, message, tx_id, NONCE, signed_tx.hex() From 26252f48c413ee8d3ee420403f224abf4457f469 Mon Sep 17 00:00:00 2001 From: hyeon Date: Mon, 19 Aug 2024 22:47:28 +0900 Subject: [PATCH 08/11] Add IAP-adhoc account to issue token easily --- .github/workflows/deploy.yml | 4 ++++ .github/workflows/main.yml | 3 +++ .github/workflows/synth.yml | 3 +++ common/shared_stack.py | 1 + common/utils/aws.py | 7 +++++-- worker/worker/issue_tokens.py | 3 ++- 6 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 74b10356..07cc2292 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -24,6 +24,8 @@ on: required: true KMS_KEY_ID: required: true + ADHOC_KEY_ID: + required: true GOOGLE_CREDENTIAL: required: true APPLE_CREDENTIAL: @@ -135,6 +137,7 @@ jobs: ODIN_GQL_URL: ${{ vars.ODIN_GQL_URL }} HEIMDALL_GQL_URL: ${{ vars.HEIMDALL_GQL_URL }} KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }} + ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }} GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }} GOOGLE_PACKAGE_NAME: ${{ vars.GOOGLE_PACKAGE_NAME }} APPLE_BUNDLE_ID: ${{ vars.APPLE_BUNDLE_ID }} @@ -172,6 +175,7 @@ jobs: ODIN_GQL_URL: ${{ vars.ODIN_GQL_URL }} HEIMDALL_GQL_URL: ${{ vars.HEIMDALL_GQL_URL }} KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }} + ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }} GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }} GOOGLE_PACKAGE_NAME: ${{ vars.GOOGLE_PACKAGE_NAME }} APPLE_BUNDLE_ID: ${{ vars.APPLE_BUNDLE_ID }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0c64ac53..8a8d86e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,6 +43,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }} + ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }} GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }} APPLE_CREDENTIAL: ${{ secrets.APPLE_CREDENTIAL }} APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }} @@ -71,6 +72,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }} + ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }} GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }} APPLE_CREDENTIAL: ${{ secrets.APPLE_CREDENTIAL }} APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }} @@ -111,6 +113,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }} + ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }} GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }} APPLE_CREDENTIAL: ${{ secrets.APPLE_CREDENTIAL }} APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }} diff --git a/.github/workflows/synth.yml b/.github/workflows/synth.yml index 8ccc9b90..a7ffb2f1 100644 --- a/.github/workflows/synth.yml +++ b/.github/workflows/synth.yml @@ -18,6 +18,8 @@ on: required: true KMS_KEY_ID: required: true + ADHOC_KMS_KEY_ID: + required: true GOOGLE_CREDENTIAL: required: true APPLE_CREDENTIAL: @@ -122,6 +124,7 @@ jobs: ODIN_GQL_URL: ${{ vars.ODIN_GQL_URL }} HEIMDALL_GQL_URL: ${{ vars.HEIMDALL_GQL_URL }} KMS_KEY_ID: ${{ secrets.KMS_KEY_ID }} + ADHOC_KMS_KEY_ID: ${{ secrets.ADHOC_KMS_KEY_ID }} GOOGLE_CREDENTIAL: ${{ secrets.GOOGLE_CREDENTIAL }} GOOGLE_PACKAGE_NAME: ${{ vars.GOOGLE_PACKAGE_NAME }} APPLE_BUNDLE_ID: ${{ vars.APPLE_BUNDLE_ID }} diff --git a/common/shared_stack.py b/common/shared_stack.py index d0571b4e..f5253400 100644 --- a/common/shared_stack.py +++ b/common/shared_stack.py @@ -94,6 +94,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: # SecureStrings in Parameter Store PARAMETER_LIST = ( ("KMS_KEY_ID", True), + ("ADHOC_KMS_KEY_ID", True), ("GOOGLE_CREDENTIAL", True), ("APPLE_CREDENTIAL", True), ("SEASON_PASS_JWT_SECRET", True), diff --git a/common/utils/aws.py b/common/utils/aws.py index 65ee1234..4cc235e4 100644 --- a/common/utils/aws.py +++ b/common/utils/aws.py @@ -21,10 +21,13 @@ def fetch_secrets(region: str, secret_arn: str) -> Dict: return json.loads(resp["SecretString"]) -def fetch_kms_key_id(stage: str, region: str) -> Optional[str]: +def fetch_kms_key_id(stage: str, region: str, adhoc: bool = False) -> Optional[str]: client = boto3.client("ssm", region_name=region) try: - return client.get_parameter(Name=f"{stage}_9c_IAP_KMS_KEY_ID", WithDecryption=True)["Parameter"]["Value"] + return client.get_parameter( + Name=f"{stage}_9c_IAP{'_ADHOC' if adhoc else ''}_KMS_KEY_ID", + WithDecryption=True + )["Parameter"]["Value"] except Exception as e: logger.error(e) return None diff --git a/worker/worker/issue_tokens.py b/worker/worker/issue_tokens.py index 2fc680d4..fd3c919d 100644 --- a/worker/worker/issue_tokens.py +++ b/worker/worker/issue_tokens.py @@ -24,6 +24,7 @@ NONCE = 0 PLANET_ID = PlanetID.XXX GQL_URL = "https://example.com/graphql" # Use Odin/Heimdall GQL host +USE_ADHOC = True # to here HEADLESS_GQL_JWT_SECRET = fetch_parameter( @@ -37,7 +38,7 @@ def issue(event, context): spec_list = [] gql = GQL(GQL_URL, HEADLESS_GQL_JWT_SECRET) - account = Account(fetch_kms_key_id(os.environ.get("STAGE"), os.environ.get("REGION_NAME"))) + account = Account(fetch_kms_key_id(os.environ.get("STAGE"), os.environ.get("REGION_NAME"), adhoc=USE_ADHOC)) for data in event: data = dict(zip(DICT_HEADER, data)) From 7144483db05efc92146b84ba607c4c26a33398df Mon Sep 17 00:00:00 2001 From: hyeon Date: Wed, 21 Aug 2024 23:16:49 +0900 Subject: [PATCH 09/11] Fix missing/wrong param --- .github/workflows/deploy.yml | 2 +- common/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 07cc2292..625cc448 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -24,7 +24,7 @@ on: required: true KMS_KEY_ID: required: true - ADHOC_KEY_ID: + ADHOC_KMS_KEY_ID: required: true GOOGLE_CREDENTIAL: required: true diff --git a/common/__init__.py b/common/__init__.py index 14a8a463..df1bc8bc 100644 --- a/common/__init__.py +++ b/common/__init__.py @@ -58,6 +58,7 @@ class Config: headless: str = "http://localhost" kms_key_id: Optional[str] = None + adhoc_kms_key_id: Optional[str] = None golden_dust_request_sheet_id: Optional[str] = None golden_dust_work_sheet_id: Optional[str] = None form_sheet: Optional[str] = None From c630bc830c908b192afe5fb78e88aea2380d3600 Mon Sep 17 00:00:00 2001 From: hyeon Date: Wed, 21 Aug 2024 23:35:38 +0900 Subject: [PATCH 10/11] Add permission to use adhoc KMS key --- worker/worker_cdk_stack.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/worker/worker_cdk_stack.py b/worker/worker_cdk_stack.py index 849ca8f5..9f94e5d4 100644 --- a/worker/worker_cdk_stack.py +++ b/worker/worker_cdk_stack.py @@ -64,6 +64,14 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: resources=[f"arn:aws:kms:{config.region_name}:{config.account_id}:key/{kms_key_id}"] ) ) + resp = ssm.get_parameter(Name=f"{config.stage}_9c_IAP_ADHOC_KMS_KEY_ID", WithDecryption=True) + kms_key_id = resp["Parameter"]["Value"] + role.add_to_policy( + _iam.PolicyStatement( + actions=["kms:GetPublicKey", "kms:Sign"], + resources=[f"arn:aws:kms:{config.region_name}:{config.account_id}:key/{kms_key_id}"] + ) + ) role.add_to_policy( _iam.PolicyStatement( actions=["ssm:GetParameter"], From 0656a179ff09e54a8e8b9c48676879de4578f149 Mon Sep 17 00:00:00 2001 From: hyeon Date: Thu, 22 Aug 2024 00:09:26 +0900 Subject: [PATCH 11/11] Add missing policy --- worker/worker_cdk_stack.py | 1 + 1 file changed, 1 insertion(+) diff --git a/worker/worker_cdk_stack.py b/worker/worker_cdk_stack.py index 9f94e5d4..1b93b217 100644 --- a/worker/worker_cdk_stack.py +++ b/worker/worker_cdk_stack.py @@ -79,6 +79,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: shared_stack.google_credential_arn, shared_stack.apple_credential_arn, shared_stack.kms_key_id_arn, + shared_stack.adhoc_kms_key_id_arn, shared_stack.voucher_jwt_secret_arn, shared_stack.headless_gql_jwt_secret_arn, ]