Skip to content

Commit c0744f9

Browse files
committed
Merge branch 'main' into konflux
2 parents 1abdba2 + 5720473 commit c0744f9

21 files changed

+1532
-27
lines changed

charon.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
%global owner Commonjava
22
%global modulename charon
33

4-
%global charon_version 1.3.3
4+
%global charon_version 1.4.0
55
%global sdist_tar_name %{modulename}-%{charon_version}
66

77
%global python3_pkgversion 3

charon/cmd/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
"""
16-
from click import group
16+
from click import group, version_option, pass_context
1717
from charon.cmd.cmd_upload import upload
1818
from charon.cmd.cmd_delete import delete
1919
from charon.cmd.cmd_index import index
2020
from charon.cmd.cmd_checksum import init_checksum, checksum
2121
from charon.cmd.cmd_cache import init_cf, cf
22+
from charon.cmd.cmd_sign import sign
2223

2324

2425
@group()
25-
def cli():
26+
@version_option()
27+
@pass_context
28+
def cli(ctx):
2629
"""Charon is a tool to synchronize several types of
2730
artifacts repository data to Red Hat Ronda
2831
service (maven.repository.redhat.com).
@@ -41,3 +44,6 @@ def cli():
4144
# init checksum command
4245
init_checksum()
4346
cli.add_command(checksum)
47+
48+
# radas sign cmd
49+
cli.add_command(sign)

charon/cmd/cmd_sign.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""
2+
Copyright (C) 2022 Red Hat, Inc. (https://github.com/Commonjava/charon)
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
from typing import List
17+
18+
from charon.config import get_config
19+
from charon.pkgs.radas_sign import sign_in_radas
20+
from charon.cmd.internal import _decide_mode
21+
from charon.constants import DEFAULT_RADAS_SIGN_IGNORES
22+
23+
from click import command, option, argument
24+
25+
import traceback
26+
import logging
27+
import sys
28+
import datetime
29+
30+
logger = logging.getLogger(__name__)
31+
32+
33+
@argument(
34+
"repo_url",
35+
type=str
36+
)
37+
@option(
38+
"--requester",
39+
"-r",
40+
help="""
41+
The requester who sends the signing request.
42+
""",
43+
required=True
44+
)
45+
@option(
46+
"--result_path",
47+
"-p",
48+
help="""
49+
The path which will save the sign result file.
50+
""",
51+
required=True
52+
)
53+
@option(
54+
"--ignore_patterns",
55+
"-i",
56+
multiple=True,
57+
help="""
58+
The regex patterns list to filter out the files which should
59+
not be allowed to upload to S3. Can accept more than one pattern.
60+
"""
61+
)
62+
@option(
63+
"--config",
64+
"-c",
65+
help="""
66+
The charon configuration yaml file path. Default is
67+
$HOME/.charon/charon.yaml
68+
"""
69+
)
70+
@option(
71+
"--sign_key",
72+
"-k",
73+
help="""
74+
rpm-sign key to be used, will replace {{ key }} in default configuration for signature.
75+
Does noting if detach_signature_command does not contain {{ key }} field.
76+
""",
77+
required=True
78+
)
79+
@option(
80+
"--debug",
81+
"-D",
82+
help="Debug mode, will print all debug logs for problem tracking.",
83+
is_flag=True,
84+
default=False
85+
)
86+
@option(
87+
"--quiet",
88+
"-q",
89+
help="Quiet mode, will shrink most of the logs except warning and errors.",
90+
is_flag=True,
91+
default=False
92+
)
93+
@command()
94+
def sign(
95+
repo_url: str,
96+
requester: str,
97+
result_path: str,
98+
sign_key: str,
99+
ignore_patterns: List[str] = None,
100+
config: str = None,
101+
debug=False,
102+
quiet=False
103+
):
104+
"""Do signing against files in the repo zip in repo_url through
105+
radas service. The repo_url points to the maven zip repository
106+
in quay.io, which will be sent as the source of the signing.
107+
"""
108+
logger.debug("%s", ignore_patterns)
109+
try:
110+
current = datetime.datetime.now().strftime("%Y%m%d%I%M")
111+
_decide_mode("radas_sign", current, is_quiet=quiet, is_debug=debug)
112+
conf = get_config(config)
113+
if not conf:
114+
logger.error("The charon configuration is not valid!")
115+
sys.exit(1)
116+
radas_conf = conf.get_radas_config()
117+
if not radas_conf or not radas_conf.validate():
118+
logger.error("The configuration for radas is not valid!")
119+
sys.exit(1)
120+
# All ignore files in global config should also be ignored in signing.
121+
ig_patterns = conf.get_ignore_patterns()
122+
ig_patterns.extend(DEFAULT_RADAS_SIGN_IGNORES)
123+
if ignore_patterns:
124+
ig_patterns.extend(ignore_patterns)
125+
ig_patterns = list(set(ig_patterns))
126+
args = {
127+
"repo_url": repo_url,
128+
"requester": requester,
129+
"sign_key": sign_key,
130+
"result_path": result_path,
131+
"ignore_patterns": ig_patterns,
132+
"radas_config": radas_conf
133+
}
134+
logger.debug("params: %s", args)
135+
sign_in_radas(**args) # type: ignore
136+
except Exception:
137+
print(traceback.format_exc())
138+
sys.exit(2)

charon/cmd/cmd_upload.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@
136136
default=False
137137
)
138138
@option("--dryrun", "-n", is_flag=True, default=False)
139+
@option(
140+
"--sign_result_loc",
141+
"-l",
142+
default="/tmp/sign",
143+
help="""
144+
The local save path for oras to pull the radas signature result.
145+
Sign request will use this path to download the signature result,
146+
Upload will use the file on this path to generate the corresponding .asc files
147+
""",
148+
)
139149
@command()
140150
def upload(
141151
repo: str,
@@ -150,7 +160,8 @@ def upload(
150160
sign_key: str = "redhatdevel",
151161
debug=False,
152162
quiet=False,
153-
dryrun=False
163+
dryrun=False,
164+
sign_result_file=None,
154165
):
155166
"""Upload all files from a released product REPO to Ronda
156167
Service. The REPO points to a product released tarball which
@@ -221,7 +232,8 @@ def upload(
221232
key=sign_key,
222233
dry_run=dryrun,
223234
manifest_bucket_name=manifest_bucket_name,
224-
config=config
235+
config=config,
236+
sign_result_file=sign_result_file
225237
)
226238
if not succeeded:
227239
sys.exit(1)

charon/config.py

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
"""
16+
1617
import logging
1718
import os
1819
from typing import Dict, List, Optional
@@ -24,6 +25,101 @@
2425
logger = logging.getLogger(__name__)
2526

2627

28+
class RadasConfig(object):
29+
def __init__(self, data: Dict):
30+
self.__umb_host: str = data.get("umb_host", None)
31+
self.__umb_host_port: str = data.get("umb_host_port", "5671")
32+
self.__result_queue: str = data.get("result_queue", None)
33+
self.__request_chan: str = data.get("request_channel", None)
34+
self.__client_ca: str = data.get("client_ca", None)
35+
self.__client_key: str = data.get("client_key", None)
36+
self.__client_key_pass_file: str = data.get("client_key_pass_file", None)
37+
self.__root_ca: str = data.get("root_ca", "/etc/pki/tls/certs/ca-bundle.crt")
38+
self.__quay_radas_registry_config: Optional[str] = data.get(
39+
"quay_radas_registry_config", None
40+
)
41+
self.__radas_sign_timeout_retry_count: int = data.get("radas_sign_timeout_retry_count", 10)
42+
self.__radas_sign_timeout_retry_interval: int = data.get(
43+
"radas_sign_timeout_retry_interval", 60
44+
)
45+
self.__radas_receiver_timeout: int = int(data.get("radas_receiver_timeout", 1800))
46+
47+
def validate(self) -> bool:
48+
if not self.__umb_host:
49+
logger.error("Missing host name setting for UMB!")
50+
return False
51+
if not self.__result_queue:
52+
logger.error("Missing the queue setting to receive signing result in UMB!")
53+
return False
54+
if not self.__request_chan:
55+
logger.error("Missing the queue setting to send signing request in UMB!")
56+
return False
57+
if self.__client_ca and not os.access(self.__client_ca, os.R_OK):
58+
logger.error("The client CA file is not valid!")
59+
return False
60+
if self.__client_key and not os.access(self.__client_key, os.R_OK):
61+
logger.error("The client key file is not valid!")
62+
return False
63+
if self.__client_key_pass_file and not os.access(self.__client_key_pass_file, os.R_OK):
64+
logger.error("The client key password file is not valid!")
65+
return False
66+
if self.__root_ca and not os.access(self.__root_ca, os.R_OK):
67+
logger.error("The root ca file is not valid!")
68+
return False
69+
if self.__quay_radas_registry_config and not os.access(
70+
self.__quay_radas_registry_config, os.R_OK
71+
):
72+
self.__quay_radas_registry_config = None
73+
logger.warning(
74+
"The quay registry config for oras is not valid, will ignore the registry config!"
75+
)
76+
return True
77+
78+
def umb_target(self) -> str:
79+
return f"amqps://{self.__umb_host.strip()}:{self.__umb_host_port}"
80+
81+
def result_queue(self) -> str:
82+
return self.__result_queue.strip()
83+
84+
def request_channel(self) -> str:
85+
return self.__request_chan.strip()
86+
87+
def client_ca(self) -> str:
88+
return self.__client_ca.strip()
89+
90+
def client_key(self) -> str:
91+
return self.__client_key.strip()
92+
93+
def client_key_password(self) -> str:
94+
pass_file = self.__client_key_pass_file
95+
if os.access(pass_file, os.R_OK):
96+
with open(pass_file, "r") as f:
97+
return f.read().strip()
98+
elif pass_file:
99+
logger.warning("The key password file is not accessible. Will ignore the password.")
100+
return ""
101+
102+
def root_ca(self) -> str:
103+
return self.__root_ca.strip()
104+
105+
def ssl_enabled(self) -> bool:
106+
return bool(self.__client_ca and self.__client_key and self.__root_ca)
107+
108+
def quay_radas_registry_config(self) -> Optional[str]:
109+
if self.__quay_radas_registry_config:
110+
return self.__quay_radas_registry_config.strip()
111+
return None
112+
113+
def radas_sign_timeout_retry_count(self) -> int:
114+
return self.__radas_sign_timeout_retry_count
115+
116+
def radas_sign_timeout_retry_interval(self) -> int:
117+
return self.__radas_sign_timeout_retry_interval
118+
119+
def receiver_timeout(self) -> int:
120+
return self.__radas_receiver_timeout
121+
122+
27123
class CharonConfig(object):
28124
"""CharonConfig is used to store all configurations for charon
29125
tools.
@@ -39,6 +135,13 @@ def __init__(self, data: Dict):
39135
self.__ignore_signature_suffix: Dict = data.get("ignore_signature_suffix", None)
40136
self.__signature_command: str = data.get("detach_signature_command", None)
41137
self.__aws_cf_enable: bool = data.get("aws_cf_enable", False)
138+
radas_config: Dict = data.get("radas", None)
139+
self.__radas_config: Optional[RadasConfig] = None
140+
if radas_config:
141+
self.__radas_config = RadasConfig(radas_config)
142+
self.__radas_enabled = bool(self.__radas_config and self.__radas_config.validate())
143+
else:
144+
self.__radas_enabled = False
42145

43146
def get_ignore_patterns(self) -> List[str]:
44147
return self.__ignore_patterns
@@ -67,19 +170,23 @@ def get_detach_signature_command(self) -> str:
67170
def is_aws_cf_enable(self) -> bool:
68171
return self.__aws_cf_enable
69172

173+
def is_radas_enabled(self) -> bool:
174+
return self.__radas_enabled
175+
176+
def get_radas_config(self) -> Optional[RadasConfig]:
177+
return self.__radas_config
178+
70179

71180
def get_config(cfgPath=None) -> CharonConfig:
72181
config_file_path = cfgPath
73182
if not config_file_path or not os.path.isfile(config_file_path):
74183
config_file_path = os.path.join(os.getenv("HOME", ""), ".charon", CONFIG_FILE)
75-
data = read_yaml_from_file_path(config_file_path, 'schemas/charon.json')
184+
data = read_yaml_from_file_path(config_file_path, "schemas/charon.json")
76185
return CharonConfig(data)
77186

78187

79188
def get_template(template_file: str) -> str:
80-
template = os.path.join(
81-
os.getenv("HOME", ''), ".charon/template", template_file
82-
)
189+
template = os.path.join(os.getenv("HOME", ""), ".charon/template", template_file)
83190
if os.path.isfile(template):
84191
with open(template, encoding="utf-8") as file_:
85192
return file_.read()

charon/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,10 @@
175175
DEFAULT_ERRORS_LOG = "errors.log"
176176

177177
DEFAULT_REGISTRY = "localhost"
178+
DEFAULT_RADAS_SIGN_TIMEOUT_RETRY_COUNT = 10
179+
DEFAULT_RADAS_SIGN_TIMEOUT_RETRY_INTERVAL = 60
180+
181+
DEFAULT_RADAS_SIGN_IGNORES = [
182+
r".*\.md5$", r".*\.sha1$", r".*\.sha128$", r".*\.sha256$",
183+
r".*\.sha512$", r".*\.asc$"
184+
]

0 commit comments

Comments
 (0)