Skip to content

Commit 1149213

Browse files
Merge branch 'main' into feat/cluster-hardening-tests
2 parents b962d02 + 53b5e45 commit 1149213

17 files changed

+1033
-48
lines changed

Standards/scs-0125-v1-secure-connections.md

Lines changed: 277 additions & 0 deletions
Large diffs are not rendered by default.

Standards/scs-0214-v1-k8s-node-distribution.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,3 @@ If the standard is used by a provider, the following decisions are binding and v
8484
[k8s-ha]: https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/
8585
[k8s-large-clusters]: https://kubernetes.io/docs/setup/best-practices/cluster-large/
8686
[scs-0213-v1]: https://github.com/SovereignCloudStack/standards/blob/main/Standards/scs-0213-v1-k8s-nodes-anti-affinity.md
87-
[k8s-labels-docs]: https://kubernetes.io/docs/reference/labels-annotations-taints/#topologykubernetesiozone

Standards/scs-0214-v2-k8s-node-distribution.md

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
---
22
title: Kubernetes Node Distribution and Availability
33
type: Standard
4-
status: Draft
4+
status: Stable
5+
stabilized_at: 2024-11-21
56
replaces: scs-0214-v1-k8s-node-distribution.md
67
track: KaaS
78
---
@@ -100,23 +101,6 @@ These labels MUST be kept up to date with the current state of the deployment.
100101
The field gets autopopulated most of the time by either the kubelet or external mechanisms
101102
like the cloud controller.
102103

103-
- `topology.scs.community/host-id`
104-
105-
This is an SCS-specific label; it MUST contain the hostID of the physical machine running
106-
the hypervisor (NOT: the hostID of a virtual machine). Here, the hostID is an arbitrary identifier,
107-
which need not contain the actual hostname, but it should nonetheless be unique to the host.
108-
This helps identify the distribution over underlying physical machines,
109-
which would be masked if VM hostIDs were used.
110-
111-
## Conformance Tests
112-
113-
The script `k8s-node-distribution-check.py` checks the nodes available with a user-provided
114-
kubeconfig file. Based on the labels `topology.scs.community/host-id`,
115-
`topology.kubernetes.io/zone`, `topology.kubernetes.io/region` and `node-role.kubernetes.io/control-plane`,
116-
the script then determines whether the nodes are distributed according to this standard.
117-
If this isn't the case, the script produces an error.
118-
It also produces warnings and informational outputs, e.g., if labels don't seem to be set.
119-
120104
## Previous standard versions
121105

122106
This is version 2 of the standard; it extends [version 1](scs-0214-v1-k8s-node-distribution.md) with the

Standards/scs-0214-w1-k8s-node-distribution-implementation-testing.md

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,15 @@ Worker nodes can also be distributed over "failure zones", but this isn't a requ
1616
Distribution must be shown through labelling, so that users can access these information.
1717

1818
Node distribution metadata is provided through the usage of the labels
19-
`topology.kubernetes.io/region`, `topology.kubernetes.io/zone` and
20-
`topology.scs.community/host-id` respectively.
21-
22-
At the moment, not all labels are set automatically by most K8s cluster utilities, which incurs
23-
additional setup and maintenance costs.
19+
`topology.kubernetes.io/region` and `topology.kubernetes.io/zone`.
2420

2521
## Automated tests
2622

27-
### Notes
28-
29-
The test for the [SCS K8s Node Distribution and Availability](https://github.com/SovereignCloudStack/standards/blob/main/Standards/scs-0214-v2-k8s-node-distribution.md)
30-
checks if control-plane nodes are distributed over different failure zones (distributed into
31-
physical machines, zones and regions) by observing their labels defined by the standard.
32-
33-
### Implementation
23+
Currently, automated testing is not readily possible because we cannot access information about
24+
the underlying host of a node (as opposed to its region and zone). Therefore, the test will only output
25+
a tentative result.
3426

35-
The script [`k8s_node_distribution_check.py`](https://github.com/SovereignCloudStack/standards/blob/main/Tests/kaas/k8s-node-distribution/k8s_node_distribution_check.py)
36-
connects to an existing K8s cluster and checks if a distribution can be detected with the labels
37-
set for the nodes of this cluster.
27+
The current implementation can be found in the script [`k8s_node_distribution_check.py`](https://github.com/SovereignCloudStack/standards/blob/main/Tests/kaas/k8s-node-distribution/k8s_node_distribution_check.py).
3828

3929
## Manual tests
4030

Tests/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
htmlcov/
22
.coverage
3+
.secret

Tests/add_subject.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env python3
2+
# vim: set ts=4 sw=4 et:
3+
#
4+
# add_subject.py
5+
#
6+
# (c) Matthias Büchse <[email protected]>
7+
# SPDX-License-Identifier: Apache-2.0
8+
import base64
9+
import getpass
10+
import os
11+
import os.path
12+
import re
13+
import shutil
14+
import signal
15+
import subprocess
16+
import sys
17+
18+
try:
19+
from passlib.context import CryptContext
20+
import argon2 # noqa:F401
21+
except ImportError:
22+
print('Missing passlib and/or argon2. Please do:\npip install passlib argon2_cffi', file=sys.stderr)
23+
sys.exit(1)
24+
25+
# see ../compliance-monitor/monitor.py
26+
CRYPTCTX = CryptContext(schemes=('argon2', 'bcrypt'), deprecated='auto')
27+
SSH_KEYGEN = shutil.which('ssh-keygen')
28+
SUBJECT_RE = re.compile(r"[a-zA-Z0-9_\-]+")
29+
30+
31+
def main(argv, cwd):
32+
if len(argv) != 1:
33+
raise RuntimeError("Need to supply precisely one argument: name of subject")
34+
subject = argv[0]
35+
print(f"Attempt to add subject {subject!r}")
36+
keyfile_path = os.path.join(cwd, '.secret', 'keyfile')
37+
tokenfile_path = os.path.join(cwd, '.secret', 'tokenfile')
38+
if os.path.exists(keyfile_path):
39+
raise RuntimeError(f"Keyfile {keyfile_path} already present. Please proceed manually")
40+
if os.path.exists(tokenfile_path):
41+
raise RuntimeError(f"Tokenfile {tokenfile_path} already present. Please proceed manually")
42+
if not SUBJECT_RE.fullmatch(subject):
43+
raise RuntimeError(f"Subject name {subject!r} using disallowed characters")
44+
sanitized_subject = subject.replace('-', '_')
45+
print("Creating API key...")
46+
while True:
47+
password = getpass.getpass("Enter passphrase: ")
48+
if password == getpass.getpass("Repeat passphrase: "):
49+
break
50+
print("No match. Try again...")
51+
token = base64.b64encode(f"{subject}:{password}".encode('utf-8'))
52+
hash_ = CRYPTCTX.hash(password)
53+
with open(tokenfile_path, "wb") as fileobj:
54+
os.fchmod(fileobj.fileno(), 0o600)
55+
fileobj.write(token)
56+
print("Creating key file using `ssh-keygen`...")
57+
subprocess.check_call([SSH_KEYGEN, '-t', 'ed25519', '-C', sanitized_subject, '-f', keyfile_path, '-N', '', '-q'])
58+
with open(keyfile_path + '.pub', "r") as fileobj:
59+
pubkey_components = fileobj.readline().split()
60+
print(f'''
61+
The following SECRET files have been created:
62+
63+
- {keyfile_path}
64+
- {tokenfile_path}
65+
66+
They are required for submitting test reports. You MUST keep them secure and safe.
67+
68+
Insert the following snippet into compliance-monitor/bootstrap.yaml:
69+
70+
- subject: {subject}
71+
api_keys:
72+
- "{hash_}"
73+
keys:
74+
- public_key: "{pubkey_components[1]}"
75+
public_key_type: "{pubkey_components[0]}"
76+
public_key_name: "primary"
77+
78+
Make sure to submit a pull request with the changed file. Otherwise, the reports cannot be submitted.
79+
''', end='')
80+
81+
82+
if __name__ == "__main__":
83+
try:
84+
sys.exit(main(sys.argv[1:], cwd=os.path.dirname(sys.argv[0]) or os.getcwd()) or 0)
85+
except RuntimeError as e:
86+
print(str(e), file=sys.stderr)
87+
sys.exit(1)
88+
except KeyboardInterrupt:
89+
print("Interrupted", file=sys.stderr)
90+
sys.exit(128 + signal.SIGINT)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Secure Connections Standard Test Suite
2+
3+
## Test Environment Setup
4+
5+
> **NOTE:** The test execution procedure does not require cloud admin rights.
6+
7+
A valid cloud configuration for the OpenStack SDK in the shape of "`clouds.yaml`" is mandatory[^1].
8+
**This file is expected to be located in the current working directory where the test script is executed unless configured otherwise.**
9+
10+
[^1]: [OpenStack Documentation: Configuring OpenStack SDK Applications](https://docs.openstack.org/openstacksdk/latest/user/config/configuration.html)
11+
12+
The test execution environment can be located on any system outside of the cloud infrastructure that has OpenStack API access.
13+
Make sure that the API access is configured properly in "`clouds.yaml`".
14+
15+
It is recommended to use a Python virtual environment[^2].
16+
Next, install the libraries required by the test suite:
17+
18+
```bash
19+
pip3 install openstacksdk sslyze
20+
```
21+
22+
> Note: the version of the sslyze library determines the [version of the Mozilla TLS recommendation JSON](https://wiki.mozilla.org/Security/Server_Side_TLS#JSON_version_of_the_recommendations) that it checks against.
23+
24+
Within this environment execute the test suite.
25+
26+
[^2]: [Python 3 Documentation: Virtual Environments and Packages](https://docs.python.org/3/tutorial/venv.html)
27+
28+
## Test Execution
29+
30+
The test suite is executed as follows:
31+
32+
```bash
33+
python3 tls-checker.py --os-cloud mycloud
34+
```
35+
36+
As an alternative to "`--os-cloud`", the "`OS_CLOUD`" environment variable may be specified instead.
37+
The parameter is used to look up the correct cloud configuration in "`clouds.yaml`".
38+
For the example command above, this file should contain a `clouds.mycloud` section like this:
39+
40+
```yaml
41+
---
42+
clouds:
43+
mycloud:
44+
auth:
45+
auth_url: ...
46+
...
47+
...
48+
```
49+
50+
For any further options consult the output of "`python3 tls-checker.py --help`".
51+
52+
### Script Behavior & Test Results
53+
54+
The script will print all actions and passed tests to `stdout`.
55+
56+
If all tests pass, the script will return with an exit code of `0`.
57+
58+
If any test fails, the script will halt, print the exact error to `stderr` and return with a non-zero exit code.
59+
60+
Any tests that indicate a recommendation of the standard is not met, will print a warning message under the corresponding endpoint output.
61+
However, unmet recommendations will not count as errors.

0 commit comments

Comments
 (0)