Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .isort.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ import_heading_thirdparty=Third-Party Libraries
import_heading_firstparty=cisagov Libraries

# Should be auto-populated by seed-isort-config hook
known_third_party=docopt,github,nacl,requests,setuptools,schema
known_third_party=docopt,github,keyring,nacl,requests,setuptools,schema
# These must be manually set to correctly separate them from third party libraries
known_first_party=
59 changes: 48 additions & 11 deletions project_setup/scripts/terraform-to-secrets
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,24 @@ state directory, within a GitHub project. It will attempt to detect the reposit
name from the project's git origin. Options exist to provide the repository name or
Terraform state manually.

It requires a Personal Access Token from GitHub that has "repo" access scope.
It requires a Personal Access Token from GitHub that has "repo" access scope. Tokens
can be saved to the keychain service for future use by using the "save" command.

Usage:
terraform-to-secrets [options] <github-personal-access-token>
terraform-to-secrets [options]
terraform-to-secrets save <github-personal-access-token>

terraform-to-secrets (-h | --help)

Options:
-d --dry-run Don't create secrets. Just log what would be created.
-h --help Show this message.
--log-level=LEVEL If specified, then the log level will be set to
-l --log-level=LEVEL If specified, then the log level will be set to
the specified value. Valid values are "debug", "info",
"warning", "error", and "critical". [default: info]
--repo=REPONAME Use provided repository name instead of detecting it.
--state=JSONFILE Read state from a file instead of asking Terraform.
-r --repo=REPONAME Use provided repository name instead of detecting it.
-s --state=JSONFILE Read state from a file instead of asking Terraform.
-t --token=PAT Specify a GitHub personal access token (PAT).
"""

# Standard Python Libraries
Expand All @@ -55,6 +59,7 @@ from typing import Any, Dict, Generator, Optional, Tuple, Union

# Third-Party Libraries
import docopt
import keyring
from nacl import encoding, public
import requests
from schema import And, Or, Schema, SchemaError, Use
Expand All @@ -63,6 +68,8 @@ from schema import And, Or, Schema, SchemaError, Use
GIT_URL_RE: re.Pattern = re.compile("(?:git@|https://)github.com[:/](.*).git")
GITHUB_SECRET_NAME_TAG: str = "GitHub_Secret_Name"
GITHUB_SECRET_TERRAFORM_LOOKUP_TAG: str = "GitHub_Secret_Terraform_Lookup"
KEYRING_SERVICE = "terraform-to-secrets"
KEYRING_USERNAME = "GitHub PAT"


def get_terraform_state(filename: str = None) -> Dict:
Expand Down Expand Up @@ -279,17 +286,20 @@ def main() -> int:
# Validate and convert arguments as needed
schema: Schema = Schema(
{
"<github-personal-access-token>": And(
str,
lambda n: len(n) == 40,
error="<github-token> must be a 40 character personal access token.",
"<github-personal-access-token>": Or(
None,
And(
str,
lambda n: len(n) == 40,
error="--token must be a 40 character personal access token.",
),
),
"--log-level": And(
str,
Use(str.lower),
lambda n: n in ("debug", "info", "warning", "error", "critical"),
error="Possible values for --log-level are "
+ "debug, info, warning, error, and critical.",
"debug, info, warning, error, and critical.",
),
"--repo": Or(
None,
Expand All @@ -299,6 +309,14 @@ def main() -> int:
error='Repository names must contain a "/"',
),
),
"--token": Or(
None,
And(
str,
lambda n: len(n) == 40,
error="--token must be a 40 character personal access token.",
),
),
str: object, # Don't care about other keys, if any
}
)
Expand All @@ -312,21 +330,40 @@ def main() -> int:

# Assign validated arguments to variables
dry_run: bool = validated_args["--dry-run"]
github_token: str = validated_args["<github-personal-access-token>"]
github_token_to_save: str = validated_args["<github-personal-access-token>"]
log_level: str = validated_args["--log-level"]
repo_name: str = validated_args["--repo"]
state_filename: str = validated_args["--state"]
github_token: str = validated_args["--token"]

# Set up logging
logging.basicConfig(
format="%(asctime)-15s %(levelname)s %(message)s", level=log_level.upper()
)

# Just save the GitHub token to the keyring and exit.
if validated_args["save"]:
logging.info("Saving the GitHub personal access token to the keyring.")
keyring.set_password(KEYRING_SERVICE, KEYRING_USERNAME, github_token_to_save)
logging.info("Success!")
return 0

# If the user does not provide a repo name we'll try to determine it from git
if not repo_name:
repo_name = get_repo_name()
logging.info(f"Using GitHub repository name: {repo_name}")

if github_token is None:
logging.debug("GitHub token not provided in arguments. Checking keyring.")
github_token = keyring.get_password(KEYRING_SERVICE, KEYRING_USERNAME)
if github_token is None:
logging.critical(
"GitHub token not provided on command line or found in keychain."
)
return -1
else:
logging.info("GitHub token retrieved from keyring.")

# Get the state from Terraform or a json file
terraform_state: Dict = get_terraform_state(state_filename)

Expand Down
9 changes: 8 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,14 @@ def package_vars(version_file):
python_requires=">=3.6",
# What does your project relate to?
keywords="documentation",
install_requires=["docopt", "PyNaCl", "setuptools >= 24.2.0", "schema", "PyGithub"],
install_requires=[
"docopt",
"keyring",
"PyNaCl",
"setuptools >= 24.2.0",
"schema",
"PyGithub",
],
extras_require={
"test": [
"pre-commit",
Expand Down