Skip to content

Commit 21b680d

Browse files
initial implementation of singature for artifacts (#169)
* initial implementation of singature for artifacts * add API doc to generate_sign function * update README file * use configuration file to controll artifacts needs sign * fix some minor problem * change suffix to exclude instead * remove unused logger * fix minor problem * change charon.yml to a proper extension, change name of suffix confi
1 parent 8913cb3 commit 21b680d

File tree

12 files changed

+410
-3
lines changed

12 files changed

+410
-3
lines changed

.github/workflows/unittests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
strategy:
1717
fail-fast: false
1818
matrix:
19-
python-version: ["3.6", "3.7", "3.8"]
19+
python-version: ["3.7", "3.8", "3.9"]
2020

2121
steps:
2222
- uses: actions/checkout@v3

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ future. And Ronda service will be hosted in AWS S3.
1414

1515
See [AWS CLi V2 installation](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html#cliv2-linux-install)
1616

17+
### [Optional] GnuPG CLI tool
18+
19+
For artifact signing using keys already in GPG keystore, not required when using exported secret key file.
20+
1721
## Installation
1822

1923
### From git
@@ -49,7 +53,7 @@ to configure AWS access credentials.
4953
### charon-upload: upload a repo to S3
5054

5155
```bash
52-
usage: charon upload $tarball --product/-p ${prod} --version/-v ${ver} [--root_path] [--ignore_patterns] [--debug]
56+
usage: charon upload $tarball --product/-p ${prod} --version/-v ${ver} [--root_path] [--ignore_patterns] [--debug] [--key_id] [--key_file] [--passphrase]
5357
```
5458

5559
This command will upload the repo in tarball to S3.

charon/cmd/command.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,27 @@
9696
be extracted, when needed.
9797
""",
9898
)
99+
@option(
100+
"--sign_keyid",
101+
"-k",
102+
help="""
103+
GPG key fingerprint to sign artifacts, cannot work with --sign_keyfile
104+
requires key already imported with gpg command --passphrase is needed.
105+
""",
106+
)
107+
@option(
108+
"--sign_keyfile",
109+
"-K",
110+
help="""
111+
GPG key file path to sign artifacts, --passphrase is needed.
112+
""",
113+
)
114+
@option(
115+
"--passphrase",
116+
help="""
117+
The passphrase for GPG key, will require input if this parameter is not set.
118+
""",
119+
)
99120
@option(
100121
"--debug",
101122
"-D",
@@ -120,6 +141,9 @@ def upload(
120141
root_path="maven-repository",
121142
ignore_patterns: List[str] = None,
122143
work_dir: str = None,
144+
sign_keyid: str = None,
145+
sign_keyfile: str = None,
146+
passphrase: str = None,
123147
debug=False,
124148
quiet=False,
125149
dryrun=False
@@ -158,6 +182,9 @@ def upload(
158182
buckets=buckets,
159183
aws_profile=aws_profile,
160184
dir_=work_dir,
185+
key_id=sign_keyid,
186+
key_file=sign_keyfile,
187+
passphrase=passphrase,
161188
dry_run=dryrun,
162189
manifest_bucket_name=manifest_bucket_name
163190
)
@@ -178,6 +205,9 @@ def upload(
178205
buckets=buckets,
179206
aws_profile=aws_profile,
180207
dir_=work_dir,
208+
key_id=sign_keyid,
209+
key_file=sign_keyfile,
210+
passphrase=passphrase,
181211
dry_run=dryrun,
182212
manifest_bucket_name=manifest_bucket_name
183213
)

charon/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def __init__(self, data: Dict):
3636
self.__aws_profile: str = data.get("aws_profile", None)
3737
self.__targets: Dict = data.get("targets", None)
3838
self.__manifest_bucket: str = data.get("manifest_bucket", None)
39+
self.__ignore_signature_suffix: Dict = data.get("ignore_signature_suffix", None)
3940

4041
def get_ignore_patterns(self) -> List[str]:
4142
return self.__ignore_patterns
@@ -52,6 +53,12 @@ def get_aws_profile(self) -> str:
5253
def get_manifest_bucket(self) -> str:
5354
return self.__manifest_bucket
5455

56+
def get_ignore_signature_suffix(self, package_type: str) -> List[str]:
57+
xartifact_list: List = self.__ignore_signature_suffix.get(package_type)
58+
if not xartifact_list:
59+
logger.error("package type %s does not have ignore artifact config.", package_type)
60+
return xartifact_list
61+
5562

5663
def get_config() -> Optional[CharonConfig]:
5764
config_file_path = os.path.join(os.getenv("HOME"), ".charon", CONFIG_FILE)

charon/pkgs/maven.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
"""
1616
from charon.utils.files import HashType
1717
import charon.pkgs.indexing as indexing
18+
import charon.pkgs.signature as signature
1819
from charon.utils.files import overwrite_file, digest, write_manifest
1920
from charon.utils.archive import extract_zip_all
2021
from charon.utils.strings import remove_prefix
2122
from charon.storage import S3Client
2223
from charon.pkgs.pkg_utils import upload_post_process, rollback_post_process
23-
from charon.config import get_template
24+
from charon.config import CharonConfig, get_template, get_config
2425
from charon.constants import (META_FILE_GEN_KEY, META_FILE_DEL_KEY,
2526
META_FILE_FAILED, MAVEN_METADATA_TEMPLATE,
2627
ARCHETYPE_CATALOG_TEMPLATE, ARCHETYPE_CATALOG_FILENAME,
@@ -260,6 +261,9 @@ def handle_maven_uploading(
260261
aws_profile=None,
261262
dir_=None,
262263
do_index=True,
264+
key_id=None,
265+
key_file=None,
266+
passphrase=None,
263267
dry_run=False,
264268
manifest_bucket_name=None
265269
) -> Tuple[str, bool]:
@@ -317,6 +321,7 @@ def handle_maven_uploading(
317321
)
318322
logger.info("Files uploading done\n")
319323
succeeded = True
324+
generated_signs = []
320325
for bucket in buckets:
321326
# 5. Do manifest uploading
322327
if not manifest_bucket_name:
@@ -383,6 +388,34 @@ def handle_maven_uploading(
383388
failed_metas.extend(_failed_metas)
384389
logger.info("archetype-catalog.xml updating done in bucket %s\n", bucket_name)
385390

391+
# 10. Generate signature file if gpg ket is provided
392+
if (key_id is not None or key_file is not None) and passphrase is not None:
393+
conf = get_config()
394+
if not conf:
395+
sys.exit(1)
396+
suffix_list = __get_suffix(PACKAGE_TYPE_MAVEN, conf)
397+
artifacts = [s for s in valid_mvn_paths if not s.endswith(tuple(suffix_list))]
398+
logger.info("Start generating signature for s3 bucket %s\n", bucket_name)
399+
(_failed_metas, _generated_signs) = signature.generate_sign(
400+
PACKAGE_TYPE_MAVEN, artifacts,
401+
top_level, prefix,
402+
s3_client, bucket_name,
403+
key_id, key_file, passphrase
404+
)
405+
failed_metas.extend(_failed_metas)
406+
generated_signs.extend(_generated_signs)
407+
logger.info("Singature generation done.\n")
408+
409+
logger.info("Start upload singature files to s3 bucket %s\n", bucket_name)
410+
_failed_metas = s3_client.upload_signatures(
411+
meta_file_paths=generated_signs,
412+
target=(bucket_name, prefix),
413+
product=None,
414+
root=top_level
415+
)
416+
failed_metas.extend(_failed_metas)
417+
logger.info("Signature uploading done.\n")
418+
386419
# this step generates index.html for each dir and add them to file list
387420
# index is similar to metadata, it will be overwritten everytime
388421
if do_index:
@@ -1011,6 +1044,12 @@ def _handle_error(err_msgs: List[str]):
10111044
pass
10121045

10131046

1047+
def __get_suffix(package_type: str, conf: CharonConfig) -> List[str]:
1048+
if package_type:
1049+
return conf.get_ignore_signature_suffix(package_type)
1050+
return []
1051+
1052+
10141053
class VersionCompareKey:
10151054
'Used as key function for version sorting'
10161055
def __init__(self, obj):

charon/pkgs/npm.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from semantic_version import compare
2525

2626
import charon.pkgs.indexing as indexing
27+
import charon.pkgs.signature as signature
28+
from charon.config import CharonConfig, get_config
2729
from charon.constants import META_FILE_GEN_KEY, META_FILE_DEL_KEY, PACKAGE_TYPE_NPM
2830
from charon.storage import S3Client
2931
from charon.utils.archive import extract_npm_tarball
@@ -70,6 +72,9 @@ def handle_npm_uploading(
7072
aws_profile=None,
7173
dir_=None,
7274
do_index=True,
75+
key_id=None,
76+
key_file=None,
77+
passphrase=None,
7378
dry_run=False,
7479
manifest_bucket_name=None
7580
) -> Tuple[str, bool]:
@@ -88,6 +93,7 @@ def handle_npm_uploading(
8893
Returns the directory used for archive processing and if uploading is successful
8994
"""
9095
client = S3Client(aws_profile=aws_profile, dry_run=dry_run)
96+
generated_signs = []
9197
for bucket in buckets:
9298
bucket_name = bucket[1]
9399
prefix = remove_prefix(bucket[2], "/")
@@ -160,6 +166,35 @@ def handle_npm_uploading(
160166
failed_metas.extend(_failed_metas)
161167
logger.info("package.json uploading done")
162168

169+
if (key_id is not None or key_file is not None) and passphrase is not None:
170+
conf = get_config()
171+
if not conf:
172+
sys.exit(1)
173+
suffix_list = __get_suffix(PACKAGE_TYPE_NPM, conf)
174+
artifacts = [s for s in valid_paths if not s.endswith(tuple(suffix_list))]
175+
if META_FILE_GEN_KEY in meta_files:
176+
artifacts.extend(meta_files[META_FILE_GEN_KEY])
177+
logger.info("Start generating signature for s3 bucket %s\n", bucket_name)
178+
(_failed_metas, _generated_signs) = signature.generate_sign(
179+
PACKAGE_TYPE_NPM, artifacts,
180+
target_dir, prefix,
181+
client, bucket_name,
182+
key_id, key_file, passphrase
183+
)
184+
failed_metas.extend(_failed_metas)
185+
generated_signs.extend(_generated_signs)
186+
logger.info("Singature generation done.\n")
187+
188+
logger.info("Start upload singature files to s3 bucket %s\n", bucket_name)
189+
_failed_metas = client.upload_signatures(
190+
meta_file_paths=generated_signs,
191+
target=(bucket_name, prefix),
192+
product=None,
193+
root=target_dir
194+
)
195+
failed_metas.extend(_failed_metas)
196+
logger.info("Signature uploading done.\n")
197+
163198
# this step generates index.html for each dir and add them to file list
164199
# index is similar to metadata, it will be overwritten everytime
165200
if do_index:
@@ -533,3 +568,9 @@ def __get_path_tree(paths: str, prefix: str) -> Set[str]:
533568
if dir_.startswith("@"):
534569
valid_dirs.add(dir_.split("/")[0])
535570
return valid_dirs
571+
572+
573+
def __get_suffix(package_type: str, conf: CharonConfig) -> List[str]:
574+
if package_type:
575+
return conf.get_ignore_signature_suffix(package_type)
576+
return []

0 commit comments

Comments
 (0)