Skip to content

Commit a7f0598

Browse files
add rpm-sign as method of signature (#170)
1 parent 21b680d commit a7f0598

File tree

5 files changed

+120
-44
lines changed

5 files changed

+120
-44
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +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
17+
### [Optional] GnuPG CLI tool or rpm-sign
1818

1919
For artifact signing using keys already in GPG keystore, not required when using exported secret key file.
20+
Can be configured to use rpm-sign for release.
2021

2122
## Installation
2223

charon/cmd/command.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,21 @@
109109
"-K",
110110
help="""
111111
GPG key file path to sign artifacts, --passphrase is needed.
112+
Will be ignored when using sign_method rpm-sign
113+
""",
114+
)
115+
@option(
116+
"--sign_method",
117+
help="""
118+
Choose 'gpg' or 'rpm-sign' to warp the actuall command, default to use gpg.
119+
key_id will be used to set key for signature.
112120
""",
113121
)
114122
@option(
115123
"--passphrase",
116124
help="""
117-
The passphrase for GPG key, will require input if this parameter is not set.
125+
The passphrase for GPG key, Necessary when using gpg as sign method.
126+
Will require input when it's not set
118127
""",
119128
)
120129
@option(
@@ -143,6 +152,7 @@ def upload(
143152
work_dir: str = None,
144153
sign_keyid: str = None,
145154
sign_keyfile: str = None,
155+
sign_method: str = "gpg",
146156
passphrase: str = None,
147157
debug=False,
148158
quiet=False,
@@ -169,6 +179,10 @@ def upload(
169179
logger.error("No AWS profile specified!")
170180
sys.exit(1)
171181

182+
if sign_method != 'gpg' and sign_method != 'rpm-sign':
183+
logger.error("Signature method must be gpg or rpm-sign")
184+
sys.exit(1)
185+
172186
archive_path = __get_local_repo(repo)
173187
npm_archive_type = detect_npm_archive(archive_path)
174188
product_key = f"{product}-{version}"
@@ -184,6 +198,7 @@ def upload(
184198
dir_=work_dir,
185199
key_id=sign_keyid,
186200
key_file=sign_keyfile,
201+
sign_method=sign_method,
187202
passphrase=passphrase,
188203
dry_run=dryrun,
189204
manifest_bucket_name=manifest_bucket_name
@@ -207,6 +222,7 @@ def upload(
207222
dir_=work_dir,
208223
key_id=sign_keyid,
209224
key_file=sign_keyfile,
225+
sign_method=sign_method,
210226
passphrase=passphrase,
211227
dry_run=dryrun,
212228
manifest_bucket_name=manifest_bucket_name

charon/pkgs/maven.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ def handle_maven_uploading(
263263
do_index=True,
264264
key_id=None,
265265
key_file=None,
266+
sign_method=None,
266267
passphrase=None,
267268
dry_run=False,
268269
manifest_bucket_name=None
@@ -389,7 +390,7 @@ def handle_maven_uploading(
389390
logger.info("archetype-catalog.xml updating done in bucket %s\n", bucket_name)
390391

391392
# 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+
if key_id is not None or key_file is not None:
393394
conf = get_config()
394395
if not conf:
395396
sys.exit(1)
@@ -400,7 +401,7 @@ def handle_maven_uploading(
400401
PACKAGE_TYPE_MAVEN, artifacts,
401402
top_level, prefix,
402403
s3_client, bucket_name,
403-
key_id, key_file, passphrase
404+
key_id, key_file, sign_method, passphrase
404405
)
405406
failed_metas.extend(_failed_metas)
406407
generated_signs.extend(_generated_signs)

charon/pkgs/npm.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def handle_npm_uploading(
7474
do_index=True,
7575
key_id=None,
7676
key_file=None,
77+
sign_method=None,
7778
passphrase=None,
7879
dry_run=False,
7980
manifest_bucket_name=None
@@ -166,7 +167,7 @@ def handle_npm_uploading(
166167
failed_metas.extend(_failed_metas)
167168
logger.info("package.json uploading done")
168169

169-
if (key_id is not None or key_file is not None) and passphrase is not None:
170+
if key_id is not None or key_file is not None:
170171
conf = get_config()
171172
if not conf:
172173
sys.exit(1)
@@ -179,7 +180,7 @@ def handle_npm_uploading(
179180
PACKAGE_TYPE_NPM, artifacts,
180181
target_dir, prefix,
181182
client, bucket_name,
182-
key_id, key_file, passphrase
183+
key_id, key_file, sign_method, passphrase
183184
)
184185
failed_metas.extend(_failed_metas)
185186
generated_signs.extend(_generated_signs)

charon/pkgs/signature.py

Lines changed: 95 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import gnupg
2222
from typing import Awaitable, Callable, List, Tuple
2323
from charon.storage import S3Client
24+
from getpass import getpass
2425

2526
logger = logging.getLogger(__name__)
2627

@@ -34,6 +35,7 @@ def generate_sign(
3435
bucket: str,
3536
key_id: str = None,
3637
key_file: str = None,
38+
sign_method: str = None,
3739
passphrase: str = None
3840
) -> Tuple[List[str], List[str]]:
3941
""" This Python function generates a digital signature for a list of metadata files using
@@ -50,11 +52,15 @@ def generate_sign(
5052
and another with the failed to generate files due to exceptions.
5153
"""
5254

55+
gpg = None
5356
if key_file is not None:
5457
gnupg_home_path = os.path.join(os.getenv("HOME"), ".charon", ".gnupg")
5558
gpg = gnupg.GPG(gnupghome=gnupg_home_path)
5659
gpg.import_keys_file(key_file)
5760

61+
if sign_method == 'gpg' and passphrase is None:
62+
passphrase = getpass('Passphrase for your gpg key:')
63+
5864
async def sign_file(
5965
filename: str, failed_paths: List[str], generated_signs: List[str]
6066
):
@@ -87,45 +93,20 @@ async def sign_file(
8793
logger.debug(".asc file %s existed, skipping", remote)
8894
return
8995

90-
command = [
91-
'gpg',
92-
'--batch',
93-
'--armor',
94-
'-u', key_id,
95-
'--passphrase', passphrase,
96-
'--sign', artifact
97-
]
98-
99-
if key_file is None:
100-
# use GPG command line tool to sign artifact if key_id is passed
101-
try:
102-
# result = await __run_cmd_async(command)
103-
result = subprocess.run(command, capture_output=True, text=True, check=True)
104-
except subprocess.CalledProcessError as e:
105-
logger.error(
106-
"Error: signature generation failed due to error: %s", e
107-
)
108-
failed_paths.append(local)
109-
return
110-
if result.returncode == 0:
111-
generated_signs.append(local)
112-
else:
113-
logger.info(
114-
"signature failed with exit code %s, message %s",
115-
result.returncode, result.stderr.decode()
116-
)
96+
if sign_method == "rpm-sign":
97+
result = detach_rpm_sign_files(key_id, artifact)
98+
elif sign_method == "gpg":
99+
result = gpg_sign_files(
100+
gpg=gpg,
101+
artifact=artifact,
102+
key_id=key_id, key_file=key_file,
103+
passphrase=passphrase
104+
)
105+
106+
if result == 0:
107+
generated_signs.append(local)
117108
else:
118-
try:
119-
with open(artifact, "rb") as f:
120-
gpg.sign_file(f, passphrase=passphrase, output=local, detach=True)
121-
generated_signs.append(local)
122-
except ValueError as e:
123-
logger.error(
124-
"Error: signature generation failed due to error: %s", e
125-
)
126-
failed_paths.append(local)
127-
128-
return
109+
failed_paths.append(local)
129110

130111
return __do_path_cut_and(
131112
file_paths=artifact_path,
@@ -134,6 +115,82 @@ async def sign_file(
134115
)
135116

136117

118+
def detach_rpm_sign_files(key: str, artifact: str) -> int:
119+
# Usage: detach_sign_files KEYNAME FILE ...
120+
121+
# let's make sure we can actually sign with the given key
122+
command = [
123+
'rpm-sign',
124+
'--list-keys',
125+
'|',
126+
'grep',
127+
key
128+
]
129+
result = subprocess.run(command, capture_output=True, text=True, check=True)
130+
if result.returncode != 0:
131+
logger.error("Key %s is not in list of allowed keys", key)
132+
return result.returncode
133+
134+
# okay, now let's actually sign this thing
135+
command = [
136+
'rpm-sign',
137+
'--detachsign',
138+
'--key',
139+
key,
140+
artifact
141+
]
142+
try:
143+
# result = await __run_cmd_async(command)
144+
result = subprocess.run(command, capture_output=True, text=True, check=True)
145+
except subprocess.CalledProcessError as e:
146+
logger.error(
147+
"Error: signature generation failed due to error: %s", e
148+
)
149+
return result.returncode
150+
151+
return 1
152+
153+
154+
def gpg_sign_files(
155+
artifact: str,
156+
gpg=None,
157+
key_id: str = None,
158+
key_file: str = None,
159+
passphrase: str = None
160+
) -> int:
161+
command = [
162+
'gpg',
163+
'--batch',
164+
'--armor',
165+
'-u', key_id,
166+
'--passphrase', passphrase,
167+
'--sign', artifact
168+
]
169+
170+
if key_file is None:
171+
# use GPG command line tool to sign artifact if key_id is passed
172+
try:
173+
# result = await __run_cmd_async(command)
174+
result = subprocess.run(command, capture_output=True, text=True, check=True)
175+
except subprocess.CalledProcessError as e:
176+
logger.error(
177+
"Error: signature generation failed due to error: %s", e
178+
)
179+
return result.returncode
180+
else:
181+
try:
182+
with open(artifact, "rb") as f:
183+
local = artifact + '.asc'
184+
gpg.sign_file(f, passphrase=passphrase, output=local, detach=True)
185+
return 0
186+
except ValueError as e:
187+
logger.error(
188+
"Error: signature generation failed due to error: %s", e
189+
)
190+
return 1
191+
return 1
192+
193+
137194
def __do_path_cut_and(
138195
file_paths: List[str],
139196
path_handler: Callable[[str, List[str], List[str], asyncio.Semaphore], Awaitable[bool]],

0 commit comments

Comments
 (0)