Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DPE-1965 - chore: use keytool snap app #130

Merged
merged 24 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6a80e54
chore: use keytool snap app
marcoppenheimer Aug 30, 2023
58672e2
docs: update keytool refs in docs
marcoppenheimer Aug 30, 2023
6e4f463
chore: remove openjdk 17 from snap install
marcoppenheimer Aug 30, 2023
3365a80
chore: bump snap rev
marcoppenheimer Sep 4, 2023
b5ec623
chore: ensure snap always installed test app
marcoppenheimer Sep 4, 2023
36e4ef2
chore: fix snap paths in test app
marcoppenheimer Sep 4, 2023
53cdc79
fix: typos
marcoppenheimer Sep 5, 2023
6e3efd5
chore: bump snap lib ver for app charm
marcoppenheimer Sep 5, 2023
b45cf97
chore: pin rev in test-app
marcoppenheimer Sep 5, 2023
032a804
chore: fix alias
marcoppenheimer Sep 5, 2023
3d218a3
chore: switch to stable tls
marcoppenheimer Sep 20, 2023
2561d05
chore: fix no java mtls app
marcoppenheimer Sep 20, 2023
999e8bb
chore: sudo command
marcoppenheimer Sep 20, 2023
c78fff6
chore: copy keystore after
marcoppenheimer Sep 21, 2023
75f3484
chore: set ci perms
marcoppenheimer Sep 21, 2023
81f409d
temp: maybe reverting?
marcoppenheimer Sep 21, 2023
7f240c9
temp: use charmed-kafka.keytool again
marcoppenheimer Sep 21, 2023
028d584
chore: maybe this time?
marcoppenheimer Sep 21, 2023
6fd7c46
temp
marcoppenheimer Sep 21, 2023
3049794
temp/cicd: add upterm debug
marcoppenheimer Sep 22, 2023
4f080bd
temp/cicd: extend sleep
marcoppenheimer Sep 22, 2023
ac4d09a
chore: finally now?
marcoppenheimer Sep 25, 2023
c66affc
cicd: remove upterm, restore skipped test
marcoppenheimer Sep 25, 2023
f273b2f
cicd: remove groups
marcoppenheimer Sep 25, 2023
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
5 changes: 5 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ jobs:
echo Skipping unstable tests
echo "mark_expression=not unstable" >> $GITHUB_OUTPUT
fi
- name: Set permissions
id: set-permissions
run: |
sudo usermod -aG root $USER
sudo -s -u ${USER} bash -c 'whoami && groups'
marcoppenheimer marked this conversation as resolved.
Show resolved Hide resolved
- name: Run integration tests
run: tox run -e ${{ matrix.tox-environments }} -- -m '${{ steps.select-tests.outputs.mark_expression }}'
env:
Expand Down
20 changes: 9 additions & 11 deletions docs/how-to/h-create-mtls-client-credentials.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,10 @@ Inject Root CA and Server CA into the truststore file:
```bash

# ---------- Truststore
keytool -keystore client.truststore.jks -storepass $KAFKA_CLIENT_TRUSTSTORE_PASSWORD -noprompt \
-importcert -alias kafka-ca -file kafka_ca.pem

keytool -keystore client.truststore.jks -storepass $KAFKA_CLIENT_TRUSTSTORE_PASSWORD -noprompt \
-importcert -alias CARoot -file ss_ca.pem
charmed-kafka.keytool -keystore client.truststore.jks -storepass $KAFKA_CLIENT_TRUSTSTORE_PASSWORD -noprompt \
-importcert -alias kafka-ca -file kafka_ca.pem
charmed-kafka.keytool -keystore client.truststore.jks -storepass $KAFKA_CLIENT_TRUSTSTORE_PASSWORD -noprompt \
-importcert -alias CARoot -file ss_ca.pem
```

### Check certificates validity
Expand All @@ -100,13 +99,12 @@ keytool -keystore client.truststore.jks -storepass $KAFKA_CLIENT_TRUSTSTORE_PASS

# ---------- Check certs validity
echo "Client certs in Keystore:"
keytool -list -keystore client.keystore.p12 -storepass $KAFKA_CLIENT_KEYSTORE_PASSWORD -rfc | grep "Alias name"
keytool -list -keystore client.keystore.p12 -storepass $KAFKA_CLIENT_KEYSTORE_PASSWORD -v | grep until
charmed-kafka.keytool -list -keystore client.keystore.p12 -storepass $KAFKA_CLIENT_KEYSTORE_PASSWORD -rfc | grep "Alias name"
charmed-kafka.keytool -list -keystore client.keystore.p12 -storepass $KAFKA_CLIENT_KEYSTORE_PASSWORD -v | grep until

echo "Server certs in Truststore:"
keytool -list -keystore client.truststore.jks -storepass $KAFKA_CLIENT_TRUSTSTORE_PASSWORD -rfc | grep "Alias name"
keytool -list -keystore client.truststore.jks -storepass $KAFKA_CLIENT_TRUSTSTORE_PASSWORD -v | grep until

charmed-kafka.keytool -list -keystore client.truststore.jks -storepass $KAFKA_CLIENT_TRUSTSTORE_PASSWORD -rfc | grep "Alias name"
charmed-kafka.keytool -list -keystore client.truststore.jks -storepass $KAFKA_CLIENT_TRUSTSTORE_PASSWORD -v | grep until
```
### mTLS
This is a mutual TLS communication which means:
Expand Down Expand Up @@ -188,4 +186,4 @@ sudo charmed-kafka.topics --bootstrap-server $KAFKA_SERVERS_MTLS --command-confi
--create --topic EXAMPLE-TOPIC

sudo charmed-kafka.topics --list --bootstrap-server $KAFKA_SERVERS_MTLS --command-config $SNAP_KAFKA_PATH/client-mtls.properties
```
```
2 changes: 1 addition & 1 deletion src/literals.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

CHARM_KEY = "kafka"
SNAP_NAME = "charmed-kafka"
CHARMED_KAFKA_SNAP_REVISION = 16
CHARMED_KAFKA_SNAP_REVISION = 19

PEER = "cluster"
ZK = "zookeeper"
Expand Down
2 changes: 1 addition & 1 deletion src/snap.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def install(self) -> bool:
"""
try:
apt.update()
apt.add_package(["snapd", "openjdk-17-jre-headless"])
apt.add_package(["snapd"])
marcoppenheimer marked this conversation as resolved.
Show resolved Hide resolved
cache = snap.SnapCache()
kafka = cache[SNAP_NAME]

Expand Down
6 changes: 3 additions & 3 deletions src/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ def set_truststore(self) -> None:
"""Adds CA to JKS truststore."""
try:
subprocess.check_output(
f"keytool -import -v -alias ca -file ca.pem -keystore truststore.jks -storepass {self.truststore_password} -noprompt",
f"charmed-kafka.keytool -import -v -alias ca -file ca.pem -keystore truststore.jks -storepass {self.truststore_password} -noprompt",
stderr=subprocess.PIPE,
shell=True,
universal_newlines=True,
Expand Down Expand Up @@ -475,7 +475,7 @@ def import_cert(self, alias: str, filename: str) -> None:
"""Add a certificate to the truststore."""
try:
subprocess.check_output(
f"keytool -import -v -alias {alias} -file {filename} -keystore truststore.jks -storepass {self.truststore_password} -noprompt",
f"charmed-kafka.keytool -import -v -alias {alias} -file {filename} -keystore truststore.jks -storepass {self.truststore_password} -noprompt",
stderr=subprocess.PIPE,
shell=True,
universal_newlines=True,
Expand All @@ -493,7 +493,7 @@ def remove_cert(self, alias: str) -> None:
"""Remove a cert from the truststore."""
try:
subprocess.check_output(
f"keytool -delete -v -alias {alias} -keystore truststore.jks -storepass {self.truststore_password} -noprompt",
f"charmed-kafka.keytool -delete -v -alias {alias} -keystore truststore.jks -storepass {self.truststore_password} -noprompt",
stderr=subprocess.PIPE,
shell=True,
universal_newlines=True,
Expand Down
105 changes: 85 additions & 20 deletions tests/integration/app-charm/lib/charms/operator_libs_linux/v1/snap.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 8
LIBPATCH = 12


# Regex to locate 7-bit C1 ANSI sequences
Expand Down Expand Up @@ -222,7 +222,7 @@ def __init__(
name,
state: SnapState,
channel: str,
revision: str,
revision: int,
confinement: str,
apps: Optional[List[Dict[str, str]]] = None,
cohort: Optional[str] = "",
Expand Down Expand Up @@ -393,6 +393,22 @@ def connect(
except CalledProcessError as e:
raise SnapError("Could not {} for snap [{}]: {}".format(_cmd, self._name, e.stderr))

def hold(self, duration: Optional[timedelta] = None) -> None:
"""Add a refresh hold to a snap.

Args:
duration: duration for the hold, or None (the default) to hold this snap indefinitely.
"""
hold_str = "forever"
if duration is not None:
seconds = round(duration.total_seconds())
hold_str = f"{seconds}s"
self._snap("refresh", [f"--hold={hold_str}"])

def unhold(self) -> None:
"""Remove the refresh hold of a snap."""
self._snap("refresh", ["--unhold"])

def restart(
self, services: Optional[List[str]] = None, reload: Optional[bool] = False
) -> None:
Expand All @@ -407,12 +423,18 @@ def restart(
args = ["restart", "--reload"] if reload else ["restart"]
self._snap_daemons(args, services)

def _install(self, channel: Optional[str] = "", cohort: Optional[str] = "") -> None:
def _install(
self,
channel: Optional[str] = "",
cohort: Optional[str] = "",
revision: Optional[int] = None,
) -> None:
"""Add a snap to the system.

Args:
channel: the channel to install from
cohort: optional, the key of a cohort that this snap belongs to
revision: optional, the revision of the snap to install
"""
cohort = cohort or self._cohort

Expand All @@ -421,6 +443,8 @@ def _install(self, channel: Optional[str] = "", cohort: Optional[str] = "") -> N
args.append("--classic")
if channel:
args.append('--channel="{}"'.format(channel))
if revision:
args.append('--revision="{}"'.format(revision))
if cohort:
args.append('--cohort="{}"'.format(cohort))

Expand All @@ -430,17 +454,23 @@ def _refresh(
self,
channel: Optional[str] = "",
cohort: Optional[str] = "",
revision: Optional[int] = None,
leave_cohort: Optional[bool] = False,
) -> None:
"""Refresh a snap.

Args:
channel: the channel to install from
cohort: optionally, specify a cohort.
revision: optionally, specify the revision of the snap to refresh
leave_cohort: leave the current cohort.
"""
channel = '--channel="{}"'.format(channel) if channel else ""
args = [channel]
args = []
if channel:
args.append('--channel="{}"'.format(channel))

if revision:
args.append('--revision="{}"'.format(revision))

if not cohort:
cohort = self._cohort
Expand Down Expand Up @@ -468,6 +498,7 @@ def ensure(
classic: Optional[bool] = False,
channel: Optional[str] = "",
cohort: Optional[str] = "",
revision: Optional[int] = None,
):
"""Ensure that a snap is in a given state.

Expand All @@ -476,6 +507,10 @@ def ensure(
classic: an (Optional) boolean indicating whether classic confinement should be used
channel: the channel to install from
cohort: optional. Specify the key of a snap cohort.
revision: optional. the revision of the snap to install/refresh

While both channel and revision could be specified, the underlying snap install/refresh
command will determine which one takes precedence (revision at this time)

Raises:
SnapError if an error is encountered
Expand All @@ -494,10 +529,10 @@ def ensure(
# We are installing or refreshing a snap.
if self._state not in (SnapState.Present, SnapState.Latest):
# The snap is not installed, so we install it.
self._install(channel, cohort)
self._install(channel, cohort, revision)
else:
# The snap is installed, but we are changing it (e.g., switching channels).
self._refresh(channel, cohort)
self._refresh(channel, cohort, revision)

self._update_snap_apps()
self._state = state
Expand Down Expand Up @@ -540,7 +575,7 @@ def state(self, state: SnapState) -> None:
self._state = state

@property
def revision(self) -> str:
def revision(self) -> int:
"""Returns the revision for a snap."""
return self._revision

Expand Down Expand Up @@ -571,6 +606,12 @@ def services(self) -> Dict:

return services

@property
def held(self) -> bool:
"""Report whether the snap has a hold."""
info = self._snap("info")
return "hold:" in info


class _UnixSocketConnection(http.client.HTTPConnection):
"""Implementation of HTTPConnection that connects to a named Unix socket."""
Expand Down Expand Up @@ -787,7 +828,7 @@ def _load_installed_snaps(self) -> None:
name=i["name"],
state=SnapState.Latest,
channel=i["channel"],
revision=i["revision"],
revision=int(i["revision"]),
confinement=i["confinement"],
apps=i.get("apps", None),
)
Expand All @@ -805,7 +846,7 @@ def _load_info(self, name) -> Snap:
name=info["name"],
state=SnapState.Available,
channel=info["channel"],
revision=info["revision"],
revision=int(info["revision"]),
confinement=info["confinement"],
apps=None,
)
Expand All @@ -815,9 +856,10 @@ def _load_info(self, name) -> Snap:
def add(
snap_names: Union[str, List[str]],
state: Union[str, SnapState] = SnapState.Latest,
channel: Optional[str] = "latest",
channel: Optional[str] = "",
classic: Optional[bool] = False,
cohort: Optional[str] = "",
revision: Optional[int] = None,
) -> Union[Snap, List[Snap]]:
"""Add a snap to the system.

Expand All @@ -829,18 +871,22 @@ def add(
classic: an (Optional) boolean specifying whether it should be added with classic
confinement. Default `False`
cohort: an (Optional) string specifying the snap cohort to use
revision: an (Optional) integer specifying the snap revision to use

Raises:
SnapError if some snaps failed to install or were not found.
"""
if not channel and not revision:
channel = "latest"

snap_names = [snap_names] if type(snap_names) is str else snap_names
if not snap_names:
raise TypeError("Expected at least one snap to add, received zero!")

if type(state) is str:
state = SnapState(state)

return _wrap_snap_operations(snap_names, state, channel, classic, cohort)
return _wrap_snap_operations(snap_names, state, channel, classic, cohort, revision)


@_cache_init
Expand All @@ -864,9 +910,10 @@ def remove(snap_names: Union[str, List[str]]) -> Union[Snap, List[Snap]]:
def ensure(
snap_names: Union[str, List[str]],
state: str,
channel: Optional[str] = "latest",
channel: Optional[str] = "",
classic: Optional[bool] = False,
cohort: Optional[str] = "",
revision: Optional[int] = None,
) -> Union[Snap, List[Snap]]:
"""Ensure specified snaps are in a given state on the system.

Expand All @@ -877,12 +924,19 @@ def ensure(
classic: an (Optional) boolean specifying whether it should be added with classic
confinement. Default `False`
cohort: an (Optional) string specifying the snap cohort to use
revision: an (Optional) integer specifying the snap revision to use

When both channel and revision are specified, the underlying snap install/refresh
command will determine the precedence (revision at the time of adding this)

Raises:
SnapError if the snap is not in the cache.
"""
if state in ("present", "latest"):
return add(snap_names, SnapState(state), channel, classic, cohort)
if not revision and not channel:
channel = "latest"

if state in ("present", "latest") or revision:
return add(snap_names, SnapState(state), channel, classic, cohort, revision)
else:
return remove(snap_names)

Expand All @@ -893,6 +947,7 @@ def _wrap_snap_operations(
channel: str,
classic: bool,
cohort: Optional[str] = "",
revision: Optional[int] = None,
) -> Union[Snap, List[Snap]]:
"""Wrap common operations for bare commands."""
snaps = {"success": [], "failed": []}
Expand All @@ -905,7 +960,9 @@ def _wrap_snap_operations(
if state is SnapState.Absent:
snap.ensure(state=SnapState.Absent)
else:
snap.ensure(state=state, classic=classic, channel=channel, cohort=cohort)
snap.ensure(
state=state, classic=classic, channel=channel, cohort=cohort, revision=revision
)
snaps["success"].append(snap)
except SnapError as e:
logger.warning("Failed to {} snap {}: {}!".format(op, s, e.message))
Expand Down Expand Up @@ -976,19 +1033,27 @@ def _system_set(config_item: str, value: str) -> None:
raise SnapError("Failed setting system config '{}' to '{}'".format(config_item, value))


def hold_refresh(days: int = 90) -> bool:
def hold_refresh(days: int = 90, forever: bool = False) -> bool:
"""Set the system-wide snap refresh hold.

Args:
days: number of days to hold system refreshes for. Maximum 90. Set to zero to remove hold.
forever: if True, will set a hold forever.
"""
# Currently the snap daemon can only hold for a maximum of 90 days
if not isinstance(days, int) or days > 90:
raise ValueError("days must be an int between 1 and 90")
if not isinstance(forever, bool):
raise TypeError("forever must be a bool")
if not isinstance(days, int):
raise TypeError("days must be an int")
if forever:
_system_set("refresh.hold", "forever")
logger.info("Set system-wide snap refresh hold to: forever")
elif days == 0:
_system_set("refresh.hold", "")
logger.info("Removed system-wide snap refresh hold")
else:
# Currently the snap daemon can only hold for a maximum of 90 days
if not 1 <= days <= 90:
raise ValueError("days must be between 1 and 90")
# Add the number of days to current time
target_date = datetime.now(timezone.utc).astimezone() + timedelta(days=days)
# Format for the correct datetime format
Expand Down
Loading