diff --git a/nodes/deploy/deploy.bash b/nodes/deploy/deploy.bash index 560ab464..72b6e47b 100755 --- a/nodes/deploy/deploy.bash +++ b/nodes/deploy/deploy.bash @@ -19,7 +19,6 @@ APP_DIR="${APP_DIR:-/home/ubuntu/moonstream}" APP_NODES_DIR="${APP_DIR}/nodes" PYTHON_ENV_DIR="${PYTHON_ENV_DIR:-/home/ubuntu/moonstream-env}" PYTHON="${PYTHON_ENV_DIR}/bin/python" -PIP="${PYTHON_ENV_DIR}/bin/pip" SECRETS_DIR="${SECRETS_DIR:-/home/ubuntu/moonstream-secrets}" PARAMETERS_ENV_PATH="${SECRETS_DIR}/app.env" AWS_SSM_PARAMETER_PATH="${AWS_SSM_PARAMETER_PATH:-/moonstream/prod}" @@ -27,7 +26,6 @@ SCRIPT_DIR="$(realpath $(dirname $0))" # Parameters scripts PARAMETERS_SCRIPT="${SCRIPT_DIR}/parameters.py" -CHECKENV_PARAMETERS_SCRIPT="${SCRIPT_DIR}/parameters.bash" CHECKENV_NODES_CONNECTIONS_SCRIPT="${SCRIPT_DIR}/nodes-connections.bash" # Service file @@ -35,6 +33,27 @@ NODE_BALANCER_SERVICE_FILE="node-balancer.service" set -eu +echo +echo +echo -e "${PREFIX_INFO} Retrieving deployment parameters" +mkdir -p "${SECRETS_DIR}" +AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION}" "${PYTHON}" "${PARAMETERS_SCRIPT}" extract -p "${AWS_SSM_PARAMETER_PATH}" -o "${PARAMETERS_ENV_PATH}" + +echo +echo +echo -e "${PREFIX_INFO} Install checkenv" +HOME=/root /usr/local/go/bin/go install github.com/bugout-dev/checkenv@latest + +echo +echo +echo -e "${PREFIX_INFO} Retrieving addition deployment parameters" +HOME=/root AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION}" $HOME/go/bin/checkenv show aws_ssm+Product:moonstream >> "${PARAMETERS_ENV_PATH}" + +echo +echo +echo -e "${PREFIX_INFO} Updating nodes connection parameters" +bash "${CHECKENV_NODES_CONNECTIONS_SCRIPT}" -v -f "${PARAMETERS_ENV_PATH}" + echo echo echo -e "${PREFIX_INFO} Building executable load balancer for nodes script with Go" @@ -43,22 +62,6 @@ cd "${APP_NODES_DIR}/node_balancer" HOME=/root /usr/local/go/bin/go build -o "${APP_NODES_DIR}/node_balancer/nodebalancer" "${APP_NODES_DIR}/node_balancer/main.go" cd "${EXEC_DIR}" -echo -echo -echo -e "${PREFIX_INFO} Retrieving deployment parameters" -mkdir -p "${SECRETS_DIR}" -AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION}" "${PYTHON}" "${PARAMETERS_SCRIPT}" extract -p "${AWS_SSM_PARAMETER_PATH}" -o "${PARAMETERS_ENV_PATH}" - -echo -echo -echo -e "${PREFIX_INFO} Retrieving addition deployment parameters" -bash "${CHECKENV_PARAMETERS_SCRIPT}" -v -p "moonstream" -o "${PARAMETERS_ENV_PATH}" - -echo -echo -echo -e "${PREFIX_INFO} Updating nodes connection parameters" -bash "${CHECKENV_NODES_CONNECTIONS_SCRIPT}" -v -f "${PARAMETERS_ENV_PATH}" - echo echo echo -e "${PREFIX_INFO} Replacing existing load balancer for nodes service definition with ${NODE_BALANCER_SERVICE_FILE}" diff --git a/nodes/deploy/node-balancer.service b/nodes/deploy/node-balancer.service index 9c2c0e27..a28b4b1e 100644 --- a/nodes/deploy/node-balancer.service +++ b/nodes/deploy/node-balancer.service @@ -11,7 +11,7 @@ WorkingDirectory=/home/ubuntu/moonstream/nodes/node_balancer EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env Restart=on-failure RestartSec=15s -ExecStart=/home/ubuntu/moonstream/nodes/node_balancer/nodebalancer -host 0.0.0.0 -port 8544 -healthcheck +ExecStart=/home/ubuntu/moonstream/nodes/node_balancer/nodebalancer -host "$(ec2metadata --local-ipv4)" -port 8544 -healthcheck SyslogIdentifier=node-balancer [Install] diff --git a/nodes/deploy/parameters.py b/nodes/deploy/parameters.py new file mode 100644 index 00000000..c8df5797 --- /dev/null +++ b/nodes/deploy/parameters.py @@ -0,0 +1,102 @@ +""" +Collect secrets from AWS SSM Parameter Store and output as environment variable exports. +""" +import argparse +from dataclasses import dataclass +import sys +from typing import Any, Dict, Iterable, List, Optional + +import boto3 + + +@dataclass +class EnvironmentVariable: + name: str + value: str + + +def get_parameters(path: str) -> List[Dict[str, Any]]: + """ + Retrieve parameters from AWS SSM Parameter Store. Decrypts any encrypted parameters. + + Relies on the appropriate environment variables to authenticate against AWS: + https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html + """ + ssm = boto3.client("ssm") + next_token: Optional[bool] = True + parameters: List[Dict[str, Any]] = [] + while next_token is not None: + kwargs = {"Path": path, "Recursive": False, "WithDecryption": True} + if next_token is not True: + kwargs["NextToken"] = next_token + response = ssm.get_parameters_by_path(**kwargs) + new_parameters = response.get("Parameters", []) + parameters.extend(new_parameters) + next_token = response.get("NextToken") + + return parameters + + +def parameter_to_env(parameter_object: Dict[str, Any]) -> EnvironmentVariable: + """ + Transforms parameters returned by the AWS SSM API into EnvironmentVariables. + """ + parameter_path = parameter_object.get("Name") + if parameter_path is None: + raise ValueError('Did not find "Name" in parameter object') + name = parameter_path.split("/")[-1].upper() + + value = parameter_object.get("Value") + if value is None: + raise ValueError('Did not find "Value" in parameter object') + + return EnvironmentVariable(name, value) + + +def env_string(env_vars: Iterable[EnvironmentVariable], with_export: bool) -> str: + """ + Produces a string which, when executed in a shell, exports the desired environment variables as + specified by env_vars. + """ + prefix = "export " if with_export else "" + return "\n".join([f'{prefix}{var.name}="{var.value}"' for var in env_vars]) + + +def extract_handler(args: argparse.Namespace) -> None: + """ + Save environment variables to file. + """ + result = env_string(map(parameter_to_env, get_parameters(args.path)), args.export) + with args.outfile as ofp: + print(result, file=ofp) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Materialize environment variables from AWS SSM Parameter Store" + ) + parser.set_defaults(func=lambda _: parser.print_help()) + subcommands = parser.add_subparsers(description="Parameters commands") + + parser_extract = subcommands.add_parser( + "extract", description="Parameters extract commands" + ) + parser_extract.set_defaults(func=lambda _: parser_extract.print_help()) + parser_extract.add_argument( + "-o", "--outfile", type=argparse.FileType("w"), default=sys.stdout + ) + parser_extract.add_argument( + "--export", + action="store_true", + help="Set to output environment strings with export statements", + ) + parser_extract.add_argument( + "-p", + "--path", + default=None, + help="SSM path from which to pull environment variables (pull is NOT recursive)", + ) + parser_extract.set_defaults(func=extract_handler) + + args = parser.parse_args() + args.func(args) diff --git a/nodes/node_balancer/cmd/routes.go b/nodes/node_balancer/cmd/routes.go index c1a59774..7a9fc92c 100644 --- a/nodes/node_balancer/cmd/routes.go +++ b/nodes/node_balancer/cmd/routes.go @@ -21,9 +21,9 @@ func pingRoute(w http.ResponseWriter, r *http.Request) { func lbHandler(w http.ResponseWriter, r *http.Request) { var blockchain string switch { - case strings.HasPrefix(r.URL.Path, "/lb/ethereum"): + case strings.HasPrefix(r.URL.Path, "/nb/ethereum"): blockchain = "ethereum" - case strings.HasPrefix(r.URL.Path, "/lb/polygon"): + case strings.HasPrefix(r.URL.Path, "/nb/polygon"): blockchain = "polygon" default: http.Error(w, fmt.Sprintf("Unacceptable blockchain provided %s", blockchain), http.StatusBadRequest) @@ -41,11 +41,11 @@ func lbHandler(w http.ResponseWriter, r *http.Request) { r.Header.Add("X-Origin-Path", r.URL.Path) switch { - case strings.HasPrefix(r.URL.Path, fmt.Sprintf("/lb/%s/ping", blockchain)): + case strings.HasPrefix(r.URL.Path, fmt.Sprintf("/nb/%s/ping", blockchain)): r.URL.Path = "/ping" peer.StatusReverseProxy.ServeHTTP(w, r) return - case strings.HasPrefix(r.URL.Path, fmt.Sprintf("/lb/%s/rpc", blockchain)): + case strings.HasPrefix(r.URL.Path, fmt.Sprintf("/nb/%s/rpc", blockchain)): r.URL.Path = "/" peer.GethReverseProxy.ServeHTTP(w, r) return diff --git a/nodes/node_balancer/configs/settings.go b/nodes/node_balancer/configs/settings.go index 09b239cc..e36ad79f 100644 --- a/nodes/node_balancer/configs/settings.go +++ b/nodes/node_balancer/configs/settings.go @@ -36,11 +36,29 @@ var MOONSTREAM_NODE_POLYGON_B_IPC_ADDR = os.Getenv("MOONSTREAM_NODE_POLYGON_B_IP var MOONSTREAM_NODE_POLYGON_IPC_PORT = os.Getenv("MOONSTREAM_NODE_POLYGON_IPC_PORT") var MOONSTREAM_NODES_SERVER_PORT = os.Getenv("MOONSTREAM_NODES_SERVER_PORT") -// Return list of NodeConfig structures -func (nc *NodeConfigList) InitNodeConfigList() { - if MOONSTREAM_NODES_SERVER_PORT == "" || MOONSTREAM_NODE_ETHEREUM_A_IPC_ADDR == "" || MOONSTREAM_NODE_ETHEREUM_B_IPC_ADDR == "" || MOONSTREAM_NODE_ETHEREUM_IPC_PORT == "" || MOONSTREAM_NODE_POLYGON_A_IPC_ADDR == "" || MOONSTREAM_NODE_POLYGON_B_IPC_ADDR == "" || MOONSTREAM_NODE_POLYGON_IPC_PORT == "" { +func checkEnvVarSet() { + if MOONSTREAM_NODE_ETHEREUM_A_IPC_ADDR == "" { + MOONSTREAM_NODE_ETHEREUM_A_IPC_ADDR = "a.ethereum.moonstream.internal" + } + if MOONSTREAM_NODE_ETHEREUM_B_IPC_ADDR == "" { + MOONSTREAM_NODE_ETHEREUM_B_IPC_ADDR = "b.ethereum.moonstream.internal" + } + + if MOONSTREAM_NODE_POLYGON_A_IPC_ADDR == "" { + MOONSTREAM_NODE_POLYGON_A_IPC_ADDR = "a.polygon.moonstream.internal" + } + if MOONSTREAM_NODE_POLYGON_B_IPC_ADDR == "" { + MOONSTREAM_NODE_POLYGON_B_IPC_ADDR = "b.polygon.moonstream.internal" + } + + if MOONSTREAM_NODES_SERVER_PORT == "" || MOONSTREAM_NODE_ETHEREUM_IPC_PORT == "" || MOONSTREAM_NODE_POLYGON_IPC_PORT == "" { log.Fatal("Some of environment variables not set") } +} + +// Return list of NodeConfig structures +func (nc *NodeConfigList) InitNodeConfigList() { + checkEnvVarSet() // Define available blockchain nodes blockchainConfigList := make([]BlockchainConfig, 0, 2)