mirror of
				https://github.com/kairos-io/kcrypt.git
				synced 2025-11-04 11:45:31 +00:00 
			
		
		
		
	Compare commits
	
		
			48 Commits
		
	
	
		
			v0.4.1
			...
			v0.6.0-rc1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					f5dc23f554 | ||
| 
						 | 
					2889a52eab | ||
| 
						 | 
					18eab4843d | ||
| 
						 | 
					3e25056dd0 | ||
| 
						 | 
					36f861fd0c | ||
| 
						 | 
					5f52d1f2f9 | ||
| 
						 | 
					7a8210d26c | ||
| 
						 | 
					6d6dfd00a1 | ||
| 
						 | 
					92c79f4e75 | ||
| 
						 | 
					a254871c22 | ||
| 
						 | 
					7e6a31f38b | ||
| 
						 | 
					0f3eee7851 | ||
| 
						 | 
					15cc284978 | ||
| 
						 | 
					b7352829ff | ||
| 
						 | 
					57aef89b02 | ||
| 
						 | 
					2dbd13c984 | ||
| 
						 | 
					ff42570ec7 | ||
| 
						 | 
					36fafa38cf | ||
| 
						 | 
					f977f72e8d | ||
| 
						 | 
					608b465afc | ||
| 
						 | 
					79fbcc2f73 | ||
| 
						 | 
					7298e9c0dd | ||
| 
						 | 
					94cfbcfe88 | ||
| 
						 | 
					1296f284cb | ||
| 
						 | 
					4cff0323af | ||
| 
						 | 
					9c7c27af89 | ||
| 
						 | 
					fa9eb79998 | ||
| 
						 | 
					11bfa53dc1 | ||
| 
						 | 
					930d7433ae | ||
| 
						 | 
					845f5984b1 | ||
| 
						 | 
					71506ff020 | ||
| 
						 | 
					27183fbce7 | ||
| 
						 | 
					018322ff00 | ||
| 
						 | 
					361e1ab817 | ||
| 
						 | 
					4234ae7034 | ||
| 
						 | 
					193a7da9ee | ||
| 
						 | 
					e0316ade0a | ||
| 
						 | 
					1c3a3ac510 | ||
| 
						 | 
					641fc6ffa7 | ||
| 
						 | 
					214ae51333 | ||
| 
						 | 
					7f397a3459 | ||
| 
						 | 
					b3d7b86591 | ||
| 
						 | 
					24240b6421 | ||
| 
						 | 
					bb144f204a | ||
| 
						 | 
					7d077c9353 | ||
| 
						 | 
					0e278a89f0 | ||
| 
						 | 
					7310c313ee | ||
| 
						 | 
					b9572125e1 | 
							
								
								
									
										28
									
								
								.github/workflows/lint.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.github/workflows/lint.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
name: Lint
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths:
 | 
			
		||||
      - '**'
 | 
			
		||||
env:
 | 
			
		||||
  FORCE_COLOR: 1
 | 
			
		||||
jobs:
 | 
			
		||||
  lint:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout code
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
      - name: Install Go
 | 
			
		||||
        uses: actions/setup-go@v4
 | 
			
		||||
      - name: Install earthly
 | 
			
		||||
        uses: Luet-lab/luet-install-action@v1
 | 
			
		||||
        with:
 | 
			
		||||
          repository: quay.io/kairos/packages
 | 
			
		||||
          packages: utils/earthly
 | 
			
		||||
      - name: Run Lint checks
 | 
			
		||||
        run: |
 | 
			
		||||
          earthly +lint
 | 
			
		||||
							
								
								
									
										10
									
								
								.github/workflows/unit-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/unit-tests.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,9 +1,9 @@
 | 
			
		||||
name: Unit tests
 | 
			
		||||
on:
 | 
			
		||||
 push:
 | 
			
		||||
   branches:
 | 
			
		||||
     - master
 | 
			
		||||
 pull_request:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - master
 | 
			
		||||
  pull_request:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  unit-tests:
 | 
			
		||||
@@ -16,7 +16,7 @@ jobs:
 | 
			
		||||
      - name: Install Go
 | 
			
		||||
        uses: actions/setup-go@v3
 | 
			
		||||
        with:
 | 
			
		||||
          go-version: '^1.18'
 | 
			
		||||
          go-version: '^1.20'
 | 
			
		||||
 | 
			
		||||
      - name: Install Ginkgo
 | 
			
		||||
        run: go install github.com/onsi/ginkgo/v2/ginkgo@v2.5.0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								.yamllint
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.yamllint
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
extends: default
 | 
			
		||||
 | 
			
		||||
rules:
 | 
			
		||||
  # 80 chars should be enough, but don't fail if a line is longer
 | 
			
		||||
  line-length:
 | 
			
		||||
    max: 150
 | 
			
		||||
    level: warning
 | 
			
		||||
 | 
			
		||||
  # accept both     key:
 | 
			
		||||
  #                   - item
 | 
			
		||||
  #
 | 
			
		||||
  # and             key:
 | 
			
		||||
  #                 - item
 | 
			
		||||
  indentation:
 | 
			
		||||
    indent-sequences: whatever
 | 
			
		||||
 | 
			
		||||
  truthy:
 | 
			
		||||
    check-keys: false
 | 
			
		||||
 | 
			
		||||
  document-start:
 | 
			
		||||
    present: false
 | 
			
		||||
							
								
								
									
										42
									
								
								Earthfile
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								Earthfile
									
									
									
									
									
								
							@@ -3,21 +3,33 @@ VERSION 0.6
 | 
			
		||||
# TODO: This needs to come from pre-built kernels in c3os repos, kcrypt included.
 | 
			
		||||
# Framework images should use our initrd
 | 
			
		||||
ARG BASE_IMAGE=quay.io/kairos/core-opensuse
 | 
			
		||||
# renovate: datasource=docker depName=golang
 | 
			
		||||
ARG GO_VERSION=1.20.2
 | 
			
		||||
ARG GOLINT_VERSION=1.52.2
 | 
			
		||||
 | 
			
		||||
build-kcrypt:
 | 
			
		||||
    FROM golang:alpine
 | 
			
		||||
    RUN apk add git
 | 
			
		||||
    COPY . /work
 | 
			
		||||
    WORKDIR /work
 | 
			
		||||
    RUN CGO_ENABLED=0 go build -o kcrypt
 | 
			
		||||
    SAVE ARTIFACT /work/kcrypt AS LOCAL kcrypt
 | 
			
		||||
    ARG VERSION="$(git describe --tags)"
 | 
			
		||||
    RUN CGO_ENABLED=0 go build -o kcrypt -ldflags "-X main.Version=$VERSION"
 | 
			
		||||
    SAVE ARTIFACT /work/kcrypt kcrypt AS LOCAL kcrypt
 | 
			
		||||
 | 
			
		||||
dracut-artifacts:
 | 
			
		||||
    FROM $BASE_IMAGE
 | 
			
		||||
    WORKDIR /build
 | 
			
		||||
    COPY --dir dracut/29kcrypt .
 | 
			
		||||
    COPY dracut/10-kcrypt.conf .
 | 
			
		||||
    SAVE ARTIFACT 29kcrypt 29kcrypt
 | 
			
		||||
    SAVE ARTIFACT 10-kcrypt.conf 10-kcrypt.conf
 | 
			
		||||
 | 
			
		||||
build-dracut:
 | 
			
		||||
    FROM $BASE_IMAGE
 | 
			
		||||
    COPY . /work
 | 
			
		||||
    COPY +build-kcrypt/kcrypt /usr/bin/kcrypt
 | 
			
		||||
    WORKDIR /work
 | 
			
		||||
    RUN cp -r dracut/* /usr/lib/dracut/modules.d
 | 
			
		||||
    RUN cp dracut.conf /etc/dracut.conf.d/10-kcrypt.conf
 | 
			
		||||
    COPY +build-kcrypt/kcrypt /usr/bin/kcrypt
 | 
			
		||||
    COPY +dracut-artifacts/29kcrypt /usr/lib/dracut/modules.d/29kcrypt
 | 
			
		||||
    COPY +dracut-artifacts/10-kcrypt.conf /etc/dracut.conf.d/10-kcrypt.conf
 | 
			
		||||
    RUN kernel=$(ls /lib/modules | head -n1) && \
 | 
			
		||||
        dracut -f "/boot/initrd-${kernel}" "${kernel}" && \
 | 
			
		||||
        ln -sf "initrd-${kernel}" /boot/initrd
 | 
			
		||||
@@ -61,3 +73,21 @@ iso:
 | 
			
		||||
    RUN sha256sum $ISO_NAME.iso > $ISO_NAME.iso.sha256
 | 
			
		||||
    SAVE ARTIFACT /build/$ISO_NAME.iso iso AS LOCAL build/$ISO_NAME.iso
 | 
			
		||||
    SAVE ARTIFACT /build/$ISO_NAME.iso.sha256 sha256 AS LOCAL build/$ISO_NAME.iso.sha256
 | 
			
		||||
 | 
			
		||||
lint:
 | 
			
		||||
    BUILD +golint
 | 
			
		||||
    BUILD +yamllint
 | 
			
		||||
 | 
			
		||||
golint:
 | 
			
		||||
    ARG GO_VERSION
 | 
			
		||||
    FROM golang:$GO_VERSION
 | 
			
		||||
    ARG GOLINT_VERSION
 | 
			
		||||
    RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v$GOLINT_VERSION
 | 
			
		||||
    WORKDIR /build
 | 
			
		||||
    COPY . .
 | 
			
		||||
    RUN golangci-lint run
 | 
			
		||||
 | 
			
		||||
yamllint:
 | 
			
		||||
    FROM cytopia/yamllint
 | 
			
		||||
    COPY . .
 | 
			
		||||
    RUN yamllint .github/workflows/
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										64
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
<h1 align="center">
 | 
			
		||||
  <br>
 | 
			
		||||
     <img width="184" alt="kairos-white-column 5bc2fe34" src="https://user-images.githubusercontent.com/2420543/193010398-72d4ba6e-7efe-4c2e-b7ba-d3a826a55b7d.png"><br>
 | 
			
		||||
    Kcrypt
 | 
			
		||||
<br>
 | 
			
		||||
</h1>
 | 
			
		||||
 | 
			
		||||
<h3 align="center">Cloud native guardian for persistent data in the edge</h3>
 | 
			
		||||
<p align="center">
 | 
			
		||||
  <a href="https://opensource.org/licenses/">
 | 
			
		||||
    <img src="https://img.shields.io/badge/licence-APL2-brightgreen"
 | 
			
		||||
         alt="license">
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://github.com/kairos-io/kcrypt/issues"><img src="https://img.shields.io/github/issues/kairos-io/kcrypt"></a>
 | 
			
		||||
  <a href="https://kairos.io/docs/" target=_blank> <img src="https://img.shields.io/badge/Documentation-blue"
 | 
			
		||||
         alt="docs"></a>
 | 
			
		||||
  <img src="https://img.shields.io/badge/made%20with-Go-blue">
 | 
			
		||||
  <img src="https://goreportcard.com/badge/github.com/kairos-io/kcrypt" alt="go report card" />
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
With Kairos you can build immutable, bootable Kubernetes and OS images for your edge devices as easily as writing a Dockerfile. Optional P2P mesh with distributed ledger automates node bootstrapping and coordination. Updating nodes is as easy as CI/CD: push a new image to your container registry and let secure, risk-free A/B atomic upgrades do the rest.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<table>
 | 
			
		||||
<tr>
 | 
			
		||||
<th align="center">
 | 
			
		||||
<img width="640" height="1px">
 | 
			
		||||
<p> 
 | 
			
		||||
<small>
 | 
			
		||||
Documentation
 | 
			
		||||
</small>
 | 
			
		||||
</p>
 | 
			
		||||
</th>
 | 
			
		||||
<th align="center">
 | 
			
		||||
<img width="640" height="1">
 | 
			
		||||
<p> 
 | 
			
		||||
<small>
 | 
			
		||||
Contribute
 | 
			
		||||
</small>
 | 
			
		||||
</p>
 | 
			
		||||
</th>
 | 
			
		||||
</tr>
 | 
			
		||||
<tr>
 | 
			
		||||
<td>
 | 
			
		||||
 | 
			
		||||
 📚 [Getting started with Kairos](https://kairos.io/docs/getting-started) <br> :bulb: [Examples](https://kairos.io/docs/examples) <br> :movie_camera: [Video](https://kairos.io/docs/media/) <br> :open_hands:[Engage with the Community](https://kairos.io/community/)
 | 
			
		||||
  
 | 
			
		||||
</td>
 | 
			
		||||
<td>
 | 
			
		||||
  
 | 
			
		||||
🙌[ CONTRIBUTING.md ]( https://github.com/kairos-io/kairos/blob/master/CONTRIBUTING.md ) <br> :raising_hand: [ GOVERNANCE ]( https://github.com/kairos-io/kairos/blob/master/GOVERNANCE.md ) <br>:construction_worker:[Code of conduct](https://github.com/kairos-io/kairos/blob/master/CODE_OF_CONDUCT.md) 
 | 
			
		||||
  
 | 
			
		||||
</td>
 | 
			
		||||
</tr>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
## Description
 | 
			
		||||
 | 
			
		||||
This is the Kairos internal component which delegates encryption and decryption of partitions in a Kairos system.
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
 | 
			
		||||
See the documentation in our website: https://kairos.io/docs/advanced/partition_encryption/.
 | 
			
		||||
@@ -7,34 +7,46 @@ GENERATOR_DIR="$2"
 | 
			
		||||
[ -z "$GENERATOR_DIR" ] && exit 1
 | 
			
		||||
[ -d "$GENERATOR_DIR" ] || mkdir "$GENERATOR_DIR"
 | 
			
		||||
 | 
			
		||||
if getargbool 0 rd.neednet; then
 | 
			
		||||
    {
 | 
			
		||||
        echo "[Unit]"
 | 
			
		||||
        echo "DefaultDependencies=no"
 | 
			
		||||
        echo "Description=kcrypt online mount"
 | 
			
		||||
        echo "Before=cos-immutable-rootfs.service"
 | 
			
		||||
        echo "After=network-online.target"
 | 
			
		||||
        echo "Wants=network-online.target"
 | 
			
		||||
        echo "[Service]"
 | 
			
		||||
        echo "Type=oneshot"
 | 
			
		||||
        echo "RemainAfterExit=no"
 | 
			
		||||
        echo "ExecStart=/sbin/kcrypt-mount-local"
 | 
			
		||||
    } > "$GENERATOR_DIR"/kcrypt.service
 | 
			
		||||
else
 | 
			
		||||
    {
 | 
			
		||||
        echo "[Unit]"
 | 
			
		||||
        echo "DefaultDependencies=no"
 | 
			
		||||
        echo "Description=kcrypt mount"
 | 
			
		||||
        echo "Before=cos-immutable-rootfs.service"
 | 
			
		||||
        echo "[Service]"
 | 
			
		||||
        echo "Type=oneshot"
 | 
			
		||||
        echo "RemainAfterExit=no"
 | 
			
		||||
        echo "ExecStart=/sbin/kcrypt-mount-local"
 | 
			
		||||
    } > "$GENERATOR_DIR"/kcrypt.service
 | 
			
		||||
oem_label=$(getarg rd.cos.oemlabel=)
 | 
			
		||||
 | 
			
		||||
## Several things indicate booting from a different media so we should not do anything
 | 
			
		||||
## rd.cos.disable is set on LIVECD and disables mounting of any type
 | 
			
		||||
if getargbool 0 rd.cos.disable; then
 | 
			
		||||
    exit 0
 | 
			
		||||
fi
 | 
			
		||||
## Netboot is set on...well, netboot obiously
 | 
			
		||||
if getargbool 0 netboot; then
 | 
			
		||||
    exit 0
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# See https://github.com/kairos-io/packages/blob/d12b12b043a71d8471454f7b4fc84c3181d2bf60/packages/system/dracut/immutable-rootfs/30cos-immutable-rootfs/cos-generator.sh#L29
 | 
			
		||||
{
 | 
			
		||||
    echo "[Unit]"
 | 
			
		||||
    echo "DefaultDependencies=no"
 | 
			
		||||
    echo "Before=immucore.service"
 | 
			
		||||
    echo "Conflicts=initrd-switch-root.target"
 | 
			
		||||
    if getargbool 0 rd.neednet; then
 | 
			
		||||
        echo "Wants=network-online.target"
 | 
			
		||||
        echo "After=network-online.target"
 | 
			
		||||
        echo "Description=kcrypt online mount"
 | 
			
		||||
    else
 | 
			
		||||
        echo "Description=kcrypt mount"
 | 
			
		||||
    fi
 | 
			
		||||
    # OEM is special as kcrypt plugins might need that in order to unlock other partitions and plugins can reside in /oem as well and kcrypt needs to find them
 | 
			
		||||
    if [ -n "${oem_label}" ]; then
 | 
			
		||||
        echo "After=oem.mount"
 | 
			
		||||
    fi
 | 
			
		||||
    echo "After=sysroot.mount"
 | 
			
		||||
    echo "[Service]"
 | 
			
		||||
    echo "Type=oneshot"
 | 
			
		||||
    echo "RemainAfterExit=no"
 | 
			
		||||
    echo "ExecStart=/usr/bin/kcrypt unlock-all"
 | 
			
		||||
} > "$GENERATOR_DIR"/kcrypt.service
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if [ ! -e "$GENERATOR_DIR/initrd-fs.target.requires/kcrypt.service" ]; then
 | 
			
		||||
    mkdir -p "$GENERATOR_DIR"/initrd-fs.target.requires
 | 
			
		||||
    ln -s "$GENERATOR_DIR"/kcrypt.service \
 | 
			
		||||
        "$GENERATOR_DIR"/initrd-fs.target.requires/kcrypt.service
 | 
			
		||||
fi
 | 
			
		||||
fi
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,7 @@ install() {
 | 
			
		||||
 | 
			
		||||
    inst_multiple \
 | 
			
		||||
        kcrypt
 | 
			
		||||
    inst_script "${moddir}/mount-local.sh" "/sbin/kcrypt-mount-local"
 | 
			
		||||
    #inst_hook pre-trigger 10 "$moddir/mount-local.sh"
 | 
			
		||||
 | 
			
		||||
    inst_script "${moddir}/generator.sh" \
 | 
			
		||||
        "${systemdutildir}/system-generators/dracut-kcrypt-generator"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
 | 
			
		||||
# ex: ts=8 sw=4 sts=4 et filetype=sh
 | 
			
		||||
 | 
			
		||||
type getarg > /dev/null 2>&1 || . /lib/dracut-lib.sh
 | 
			
		||||
 | 
			
		||||
PATH=/usr/sbin:/usr/bin:/sbin:/bin
 | 
			
		||||
 | 
			
		||||
OEM=$(blkid -L COS_OEM)
 | 
			
		||||
if [ "$OEM" != "" ]; then
 | 
			
		||||
    mkdir /oem
 | 
			
		||||
    mount $OEM /oem
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
kcrypt unlock-all
 | 
			
		||||
 | 
			
		||||
if [ "$OEM" != "" ]; then
 | 
			
		||||
umount /oem
 | 
			
		||||
fi
 | 
			
		||||
							
								
								
									
										3
									
								
								earthly.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								earthly.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
docker run --privileged -v /var/run/docker.sock:/var/run/docker.sock --rm -t -v $(pwd):/workspace -v earthly-tmp:/tmp/earthly:rw earthly/earthly:v0.6.21 --allow-privileged $@
 | 
			
		||||
							
								
								
									
										67
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								go.mod
									
									
									
									
									
								
							@@ -1,38 +1,69 @@
 | 
			
		||||
module github.com/kairos-io/kcrypt
 | 
			
		||||
 | 
			
		||||
go 1.18
 | 
			
		||||
go 1.20
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/anatol/luks.go v0.0.0-20220803222236-155595903818
 | 
			
		||||
	github.com/anatol/luks.go v0.0.0-20230125211543-ada2562d4206
 | 
			
		||||
	github.com/gofrs/uuid v4.4.0+incompatible
 | 
			
		||||
	github.com/hashicorp/go-multierror v1.1.1
 | 
			
		||||
	github.com/jaypipes/ghw v0.9.0
 | 
			
		||||
	github.com/mudler/go-pluggable v0.0.0-20220716112424-189d463e3ff3
 | 
			
		||||
	github.com/onsi/ginkgo/v2 v2.5.0
 | 
			
		||||
	github.com/onsi/gomega v1.24.0
 | 
			
		||||
	github.com/otiai10/copy v1.7.0
 | 
			
		||||
	github.com/jaypipes/ghw v0.10.0
 | 
			
		||||
	github.com/kairos-io/kairos v1.24.3-56.0.20230329142538-b6ae4b58c07d
 | 
			
		||||
	github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5
 | 
			
		||||
	github.com/onsi/ginkgo/v2 v2.9.2
 | 
			
		||||
	github.com/onsi/gomega v1.27.5
 | 
			
		||||
	github.com/otiai10/copy v1.9.0
 | 
			
		||||
	github.com/pkg/errors v0.9.1
 | 
			
		||||
	github.com/urfave/cli v1.22.9
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1
 | 
			
		||||
	github.com/urfave/cli v1.22.12
 | 
			
		||||
	gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	atomicgo.dev/cursor v0.1.1 // indirect
 | 
			
		||||
	atomicgo.dev/keyboard v0.2.9 // indirect
 | 
			
		||||
	github.com/StackExchange/wmi v1.2.1 // indirect
 | 
			
		||||
	github.com/anatol/devmapper.go v0.0.0-20220716012224-693a1447fc15 // indirect
 | 
			
		||||
	github.com/anatol/devmapper.go v0.0.0-20220907161421-ba4de5fc0fd1 // indirect
 | 
			
		||||
	github.com/avast/retry-go v3.0.0+incompatible // indirect
 | 
			
		||||
	github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
 | 
			
		||||
	github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 // indirect
 | 
			
		||||
	github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
 | 
			
		||||
	github.com/containerd/console v1.0.3 // indirect
 | 
			
		||||
	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 | 
			
		||||
	github.com/denisbrodbeck/machineid v1.0.1 // indirect
 | 
			
		||||
	github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d // indirect
 | 
			
		||||
	github.com/ghodss/yaml v1.0.0 // indirect
 | 
			
		||||
	github.com/go-logr/logr v1.2.3 // indirect
 | 
			
		||||
	github.com/go-ole/go-ole v1.2.6 // indirect
 | 
			
		||||
	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
 | 
			
		||||
	github.com/google/go-cmp v0.5.9 // indirect
 | 
			
		||||
	github.com/hashicorp/errwrap v1.0.0 // indirect
 | 
			
		||||
	github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
 | 
			
		||||
	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 | 
			
		||||
	github.com/gookit/color v1.5.2 // indirect
 | 
			
		||||
	github.com/hashicorp/errwrap v1.1.0 // indirect
 | 
			
		||||
	github.com/imdario/mergo v0.3.15 // indirect
 | 
			
		||||
	github.com/itchyny/gojq v0.12.12 // indirect
 | 
			
		||||
	github.com/itchyny/timefmt-go v0.1.5 // indirect
 | 
			
		||||
	github.com/jaypipes/pcidb v1.0.0 // indirect
 | 
			
		||||
	github.com/joho/godotenv v1.5.1 // indirect
 | 
			
		||||
	github.com/kairos-io/kairos-sdk v0.0.2-0.20230317135804-ad3c0f6cd6dd // indirect
 | 
			
		||||
	github.com/kr/text v0.1.0 // indirect
 | 
			
		||||
	github.com/lithammer/fuzzysearch v1.1.5 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.17 // indirect
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.14 // indirect
 | 
			
		||||
	github.com/mitchellh/go-homedir v1.1.0 // indirect
 | 
			
		||||
	github.com/russross/blackfriday/v2 v2.0.1 // indirect
 | 
			
		||||
	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
 | 
			
		||||
	golang.org/x/net v0.1.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.1.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.4.0 // indirect
 | 
			
		||||
	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
 | 
			
		||||
	github.com/pterm/pterm v0.12.57 // indirect
 | 
			
		||||
	github.com/qeesung/image2ascii v1.0.1 // indirect
 | 
			
		||||
	github.com/rivo/uniseg v0.4.4 // indirect
 | 
			
		||||
	github.com/rogpeppe/go-internal v1.9.0 // indirect
 | 
			
		||||
	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 | 
			
		||||
	github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect
 | 
			
		||||
	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
 | 
			
		||||
	golang.org/x/crypto v0.7.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.8.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.6.0 // indirect
 | 
			
		||||
	golang.org/x/term v0.6.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.8.0 // indirect
 | 
			
		||||
	golang.org/x/tools v0.7.0 // indirect
 | 
			
		||||
	gopkg.in/yaml.v2 v2.4.0 // indirect
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
			
		||||
	howett.net/plist v1.0.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										198
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										198
									
								
								go.sum
									
									
									
									
									
								
							@@ -1,19 +1,43 @@
 | 
			
		||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
			
		||||
atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4=
 | 
			
		||||
atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU=
 | 
			
		||||
atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
 | 
			
		||||
atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
 | 
			
		||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
			
		||||
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
 | 
			
		||||
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
 | 
			
		||||
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
 | 
			
		||||
github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
 | 
			
		||||
github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
 | 
			
		||||
github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
 | 
			
		||||
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
 | 
			
		||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
 | 
			
		||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
 | 
			
		||||
github.com/anatol/devmapper.go v0.0.0-20220716012224-693a1447fc15 h1:741Z9HQjbLXe4SnZ6liQTYINeD559oFl/vOtzA21KM0=
 | 
			
		||||
github.com/anatol/devmapper.go v0.0.0-20220716012224-693a1447fc15/go.mod h1:Ow7/kdG1m4K6UDTOwJeYJv9bkSLoL44qSu7S/Bxxi4Y=
 | 
			
		||||
github.com/anatol/luks.go v0.0.0-20220803222236-155595903818 h1:IyYgXFMhnSIbkDnDrTnMrPDYlc/uYhG5UcGX6NVkO58=
 | 
			
		||||
github.com/anatol/luks.go v0.0.0-20220803222236-155595903818/go.mod h1:1lE8PgTi0cS+3YsxbWcpfvGfjiPrRdcKJZc9j70OOTs=
 | 
			
		||||
github.com/anatol/devmapper.go v0.0.0-20220907161421-ba4de5fc0fd1 h1:6ok4FQsJFooNYKiSmrVUv476cG/NYmbM0LxazuL4sZU=
 | 
			
		||||
github.com/anatol/devmapper.go v0.0.0-20220907161421-ba4de5fc0fd1/go.mod h1:k5R4Ie9eQbP4muJljLeun3f1onpMKRSqG/aXMFdCWm4=
 | 
			
		||||
github.com/anatol/luks.go v0.0.0-20230125211543-ada2562d4206 h1:9kfvAJRm75SuSR185WrbYOco0cKFo9IglQfiFyRNvK0=
 | 
			
		||||
github.com/anatol/luks.go v0.0.0-20230125211543-ada2562d4206/go.mod h1:XwLorksvNshI9TH9UOO/WfX7kYIdlljCQi5WPkN7K0c=
 | 
			
		||||
github.com/anatol/vmtest v0.0.0-20220413190228-7a42f1f6d7b8 h1:t4JGeY9oaF5LB4Rdx9e2wARRRPAYt8Ow4eCf5SwO3fA=
 | 
			
		||||
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
 | 
			
		||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
 | 
			
		||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 | 
			
		||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo=
 | 
			
		||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
 | 
			
		||||
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4=
 | 
			
		||||
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 | 
			
		||||
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
 | 
			
		||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
 | 
			
		||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
 | 
			
		||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE=
 | 
			
		||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
 | 
			
		||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 | 
			
		||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 | 
			
		||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
 | 
			
		||||
@@ -21,6 +45,10 @@ github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
 | 
			
		||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 | 
			
		||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
 | 
			
		||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 | 
			
		||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
 | 
			
		||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
 | 
			
		||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
 | 
			
		||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 | 
			
		||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 | 
			
		||||
@@ -28,78 +56,136 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 | 
			
		||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 | 
			
		||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 | 
			
		||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 | 
			
		||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
 | 
			
		||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
			
		||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
			
		||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 | 
			
		||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
			
		||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
 | 
			
		||||
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
 | 
			
		||||
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
 | 
			
		||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 | 
			
		||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 | 
			
		||||
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
 | 
			
		||||
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
 | 
			
		||||
github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
 | 
			
		||||
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
 | 
			
		||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 | 
			
		||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
 | 
			
		||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 | 
			
		||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
 | 
			
		||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 | 
			
		||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
			
		||||
github.com/jaypipes/ghw v0.9.0 h1:TWF4wNIGtZcgDJaiNcFgby5BR8s2ixcUe0ydxNO2McY=
 | 
			
		||||
github.com/jaypipes/ghw v0.9.0/go.mod h1:dXMo19735vXOjpIBDyDYSp31sB2u4hrtRCMxInqQ64k=
 | 
			
		||||
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
 | 
			
		||||
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
 | 
			
		||||
github.com/itchyny/gojq v0.12.12 h1:x+xGI9BXqKoJQZkr95ibpe3cdrTbY8D9lonrK433rcA=
 | 
			
		||||
github.com/itchyny/gojq v0.12.12/go.mod h1:j+3sVkjxwd7A7Z5jrbKibgOLn0ZfLWkV+Awxr/pyzJE=
 | 
			
		||||
github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
 | 
			
		||||
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
 | 
			
		||||
github.com/jaypipes/ghw v0.10.0 h1:UHu9UX08Py315iPojADFPOkmjTsNzHj4g4adsNKKteY=
 | 
			
		||||
github.com/jaypipes/ghw v0.10.0/go.mod h1:jeJGbkRB2lL3/gxYzNYzEDETV1ZJ56OKr+CSeSEym+g=
 | 
			
		||||
github.com/jaypipes/pcidb v1.0.0 h1:vtZIfkiCUE42oYbJS0TAq9XSfSmcsgo9IdxSm9qzYU8=
 | 
			
		||||
github.com/jaypipes/pcidb v1.0.0/go.mod h1:TnYUvqhPBzCKnH34KrIX22kAeEbDCSRJ9cqLRCuNDfk=
 | 
			
		||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 | 
			
		||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 | 
			
		||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
			
		||||
github.com/kairos-io/kairos v1.24.3-56.0.20230329142538-b6ae4b58c07d h1:B01GinEZbowPwbWrqDIb6n2AaHIscJVOqsh0I5gAEXw=
 | 
			
		||||
github.com/kairos-io/kairos v1.24.3-56.0.20230329142538-b6ae4b58c07d/go.mod h1:2aYSSCHw8csfuqA5g6BpxBJ89kZt84G5okeuJj7PH+w=
 | 
			
		||||
github.com/kairos-io/kairos-sdk v0.0.2-0.20230317135804-ad3c0f6cd6dd h1:x3pwiwfj/eAv3OZ8BNFdmpjhh8ZInu7z8Xv/6+dhmFw=
 | 
			
		||||
github.com/kairos-io/kairos-sdk v0.0.2-0.20230317135804-ad3c0f6cd6dd/go.mod h1:Wg/jfAQe8seka5VUXtcPvg+sA6GmQEy+DYlJmgKM8Zs=
 | 
			
		||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 | 
			
		||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 | 
			
		||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 | 
			
		||||
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
 | 
			
		||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 | 
			
		||||
github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c=
 | 
			
		||||
github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 | 
			
		||||
github.com/mudler/go-pluggable v0.0.0-20220716112424-189d463e3ff3 h1:t4X6t8WisUy5mExfS58RBOkzaEGmuor5kOUMQS8lT2g=
 | 
			
		||||
github.com/mudler/go-pluggable v0.0.0-20220716112424-189d463e3ff3/go.mod h1:WmKcT8ONmhDQIqQ+HxU+tkGWjzBEyY/KFO8LTGCu4AI=
 | 
			
		||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 | 
			
		||||
github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5 h1:FaZD86+A9mVt7lh9glAryzQblMsbJYU2VnrdZ8yHlTs=
 | 
			
		||||
github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5/go.mod h1:WmKcT8ONmhDQIqQ+HxU+tkGWjzBEyY/KFO8LTGCu4AI=
 | 
			
		||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
 | 
			
		||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 | 
			
		||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 | 
			
		||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 | 
			
		||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
			
		||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 | 
			
		||||
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
 | 
			
		||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
 | 
			
		||||
github.com/onsi/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls=
 | 
			
		||||
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
 | 
			
		||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 | 
			
		||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
 | 
			
		||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
 | 
			
		||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 | 
			
		||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 | 
			
		||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
 | 
			
		||||
github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg=
 | 
			
		||||
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
 | 
			
		||||
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
 | 
			
		||||
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
 | 
			
		||||
github.com/onsi/gomega v1.27.5 h1:T/X6I0RNFw/kTqgfkZPcQ5KU6vCnWNBGdtrIx2dpGeQ=
 | 
			
		||||
github.com/onsi/gomega v1.27.5/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
 | 
			
		||||
github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4=
 | 
			
		||||
github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI=
 | 
			
		||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
 | 
			
		||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
 | 
			
		||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
 | 
			
		||||
github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI=
 | 
			
		||||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
 | 
			
		||||
github.com/otiai10/mint v1.4.0 h1:umwcf7gbpEwf7WFzqmWwSv0CzbeMsae2u9ZvpP8j2q4=
 | 
			
		||||
github.com/otiai10/mint v1.4.0/go.mod h1:gifjb2MYOoULtKLqUAEILUG/9KONW6f7YsJ6vQLTlFI=
 | 
			
		||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 | 
			
		||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 | 
			
		||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 | 
			
		||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 | 
			
		||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 | 
			
		||||
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
 | 
			
		||||
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
 | 
			
		||||
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
 | 
			
		||||
github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
 | 
			
		||||
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
 | 
			
		||||
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
 | 
			
		||||
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
 | 
			
		||||
github.com/pterm/pterm v0.12.57 h1:HTjDUmILmh6hIsEidRdpxQAiqcoHCdvRCxIR3KZ0/XE=
 | 
			
		||||
github.com/pterm/pterm v0.12.57/go.mod h1:7rswprkyxYOse1IMh79w42jvReNHxro4z9oHfqjIdzM=
 | 
			
		||||
github.com/qeesung/image2ascii v1.0.1 h1:Fe5zTnX/v/qNC3OC4P/cfASOXS501Xyw2UUcgrLgtp4=
 | 
			
		||||
github.com/qeesung/image2ascii v1.0.1/go.mod h1:kZKhyX0h2g/YXa/zdJR3JnLnJ8avHjZ3LrvEKSYyAyU=
 | 
			
		||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 | 
			
		||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
 | 
			
		||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
			
		||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 | 
			
		||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 | 
			
		||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
			
		||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
			
		||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
			
		||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
 | 
			
		||||
github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef h1:7D6Nm4D6f0ci9yttWaKjM1TMAXrH5Su72dojqYGntFY=
 | 
			
		||||
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
 | 
			
		||||
github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 | 
			
		||||
github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8=
 | 
			
		||||
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
 | 
			
		||||
github.com/wayneashleyberry/terminal-dimensions v1.1.0 h1:EB7cIzBdsOzAgmhTUtTTQXBByuPheP/Zv1zL2BRPY6g=
 | 
			
		||||
github.com/wayneashleyberry/terminal-dimensions v1.1.0/go.mod h1:2lc/0eWCObmhRczn2SdGSQtgBooLUzIotkkEGXqghyg=
 | 
			
		||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
 | 
			
		||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
 | 
			
		||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
			
		||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
 | 
			
		||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 | 
			
		||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
			
		||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
			
		||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
 | 
			
		||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 | 
			
		||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
 | 
			
		||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 | 
			
		||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
@@ -111,15 +197,30 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
			
		||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
 | 
			
		||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 | 
			
		||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
 | 
			
		||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
 | 
			
		||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
			
		||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
 | 
			
		||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
 | 
			
		||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 | 
			
		||||
@@ -127,19 +228,22 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
 | 
			
		||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 | 
			
		||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
 | 
			
		||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 | 
			
		||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
 | 
			
		||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 | 
			
		||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 | 
			
		||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
 | 
			
		||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 | 
			
		||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										140
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								main.go
									
									
									
									
									
								
							@@ -2,29 +2,30 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	luks "github.com/anatol/luks.go"
 | 
			
		||||
	multierror "github.com/hashicorp/go-multierror"
 | 
			
		||||
	"github.com/gofrs/uuid"
 | 
			
		||||
	"github.com/jaypipes/ghw"
 | 
			
		||||
	"github.com/jaypipes/ghw/pkg/block"
 | 
			
		||||
	"github.com/kairos-io/kcrypt/pkg/bus"
 | 
			
		||||
	"github.com/mudler/go-pluggable"
 | 
			
		||||
	configpkg "github.com/kairos-io/kcrypt/pkg/config"
 | 
			
		||||
	"github.com/kairos-io/kcrypt/pkg/lib"
 | 
			
		||||
	cp "github.com/otiai10/copy"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
 | 
			
		||||
	pi "github.com/kairos-io/kcrypt/pkg/partition_info"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var Version = "v0.0.0-dev"
 | 
			
		||||
 | 
			
		||||
func waitdevice(device string, attempts int) error {
 | 
			
		||||
	for tries := 0; tries < attempts; tries++ {
 | 
			
		||||
		sh("udevadm settle")
 | 
			
		||||
		_, err := os.Lstat(device)
 | 
			
		||||
		_, err := sh("udevadm settle")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		_, err = os.Lstat(device)
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
@@ -33,49 +34,6 @@ func waitdevice(device string, attempts int) error {
 | 
			
		||||
	return fmt.Errorf("no device found")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: Ask to discovery a pass to unlock. keep waiting until we get it and a timeout is exhausted with retrials (exp backoff)
 | 
			
		||||
func getPassword(b *block.Partition) (password string, err error) {
 | 
			
		||||
	bus.Reload()
 | 
			
		||||
 | 
			
		||||
	bus.Manager.Response(bus.EventDiscoveryPassword, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
 | 
			
		||||
		password = r.Data
 | 
			
		||||
		if r.Errored() {
 | 
			
		||||
			err = fmt.Errorf("failed discovery: %s", r.Error)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	bus.Manager.Publish(bus.EventDiscoveryPassword, b)
 | 
			
		||||
 | 
			
		||||
	if password == "" {
 | 
			
		||||
		return password, fmt.Errorf("received empty password")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func luksUnlock(device, mapper, password string) error {
 | 
			
		||||
	dev, err := luks.Open(device)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// handle error
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer dev.Close()
 | 
			
		||||
 | 
			
		||||
	err = dev.Unlock(0, []byte(password), mapper)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func unlockDisk(b *block.Partition) error {
 | 
			
		||||
	pass, err := getPassword(b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error retreiving password remotely: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return luksUnlock(fmt.Sprintf("/dev/%s", b.Name), b.Name, pass)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createLuks(dev, password, version string, cryptsetupArgs ...string) error {
 | 
			
		||||
	if version == "" {
 | 
			
		||||
		version = "luks2"
 | 
			
		||||
@@ -86,27 +44,14 @@ func createLuks(dev, password, version string, cryptsetupArgs ...string) error {
 | 
			
		||||
	cmd.Stdin = strings.NewReader(password)
 | 
			
		||||
	cmd.Stdout = os.Stdout
 | 
			
		||||
	cmd.Stderr = os.Stderr
 | 
			
		||||
 | 
			
		||||
	if err := cmd.Run(); err != nil {
 | 
			
		||||
	err := cmd.Run()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createDiskImage() (*os.File, error) {
 | 
			
		||||
	disk, err := ioutil.TempFile("", "luksv2.go.disk")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := disk.Truncate(24 * 1024 * 1024); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return disk, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: A crypt disk utility to call after install, that with discovery discoveries the password that should be used
 | 
			
		||||
// this function should delete COS_PERSISTENT. delete the partition and create a luks+type in place.
 | 
			
		||||
 | 
			
		||||
@@ -123,19 +68,20 @@ func luksify(label string) (string, error) {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pass, err := getPassword(b)
 | 
			
		||||
	pass, err := lib.GetPassword(b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	persistent = fmt.Sprintf("/dev/%s", persistent)
 | 
			
		||||
	devMapper := fmt.Sprintf("/dev/mapper/%s", b.Name)
 | 
			
		||||
	partUUID := uuid.NewV5(uuid.NamespaceURL, label)
 | 
			
		||||
 | 
			
		||||
	if err := createLuks(persistent, pass, "luks1"); err != nil {
 | 
			
		||||
	if err := createLuks(persistent, pass, "luks1", []string{"--uuid", partUUID.String()}...); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := luksUnlock(persistent, b.Name, pass); err != nil {
 | 
			
		||||
	if err := lib.LuksUnlock(persistent, b.Name, pass); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -143,8 +89,8 @@ func luksify(label string) (string, error) {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out, err := sh(fmt.Sprintf("mkfs.ext4 -L %s %s", label, devMapper))
 | 
			
		||||
 | 
			
		||||
	cmd := fmt.Sprintf("mkfs.ext4 -L %s %s", label, devMapper)
 | 
			
		||||
	out, err := sh(cmd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("err: %w, out: %s", err, out)
 | 
			
		||||
	}
 | 
			
		||||
@@ -154,15 +100,15 @@ func luksify(label string) (string, error) {
 | 
			
		||||
		return "", fmt.Errorf("err: %w, out: %s", err, out2)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return pi.PartitionToString(b), nil
 | 
			
		||||
	return configpkg.PartitionToString(b), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func findPartition(label string) (string, *block.Partition, error) {
 | 
			
		||||
	block, err := ghw.Block()
 | 
			
		||||
	b, err := ghw.Block()
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		for _, disk := range block.Disks {
 | 
			
		||||
		for _, disk := range b.Disks {
 | 
			
		||||
			for _, p := range disk.Partitions {
 | 
			
		||||
				if p.Label == label {
 | 
			
		||||
				if p.FilesystemLabel == label {
 | 
			
		||||
					return p.Name, p, nil
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
@@ -209,9 +155,13 @@ func detect(archive string) (string, error) {
 | 
			
		||||
 | 
			
		||||
// TODO: replace with golang native code
 | 
			
		||||
func extractInitrd(initrd string, dst string) error {
 | 
			
		||||
	os.MkdirAll(dst, os.ModePerm)
 | 
			
		||||
	var out string
 | 
			
		||||
	var err error
 | 
			
		||||
	err = os.MkdirAll(dst, os.ModePerm)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	format, err := detect(initrd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -255,7 +205,7 @@ func injectInitrd(initrd string, file, dst string) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	tmp, err := ioutil.TempDir("", "kcrypt")
 | 
			
		||||
	tmp, err := os.MkdirTemp("", "kcrypt")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("cannot create tempdir, %s", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -276,39 +226,13 @@ func injectInitrd(initrd string, file, dst string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: a custom toolkit version, to build out initrd pre-built with this component
 | 
			
		||||
func unlockAll() error {
 | 
			
		||||
	bus.Manager.Initialize()
 | 
			
		||||
 | 
			
		||||
	partitionInfo, err := pi.NewPartitionInfoFromFile(pi.DefaultPartitionInfoFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	block, err := ghw.Block()
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		for _, disk := range block.Disks {
 | 
			
		||||
			for _, p := range disk.Partitions {
 | 
			
		||||
				if p.Type == "crypto_LUKS" {
 | 
			
		||||
					p.Label = partitionInfo.LookupLabelForUUID(p.UUID)
 | 
			
		||||
					fmt.Printf("Unmounted Luks found at '%s' LABEL '%s' \n", p.Name, p.Label)
 | 
			
		||||
					err = multierror.Append(err, unlockDisk(p))
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						fmt.Printf("Unlocking failed: '%s'\n", err.Error())
 | 
			
		||||
					}
 | 
			
		||||
					time.Sleep(10 * time.Second)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	app := &cli.App{
 | 
			
		||||
		Name:        "keiros-kcrypt",
 | 
			
		||||
		Version:     "0.1",
 | 
			
		||||
		Name:        "kairos-kcrypt",
 | 
			
		||||
		Version:     Version,
 | 
			
		||||
		Author:      "Ettore Di Giacinto",
 | 
			
		||||
		Usage:       "keiros escrow key agent component",
 | 
			
		||||
		Usage:       "kairos escrow key agent component",
 | 
			
		||||
		Description: ``,
 | 
			
		||||
		UsageText:   ``,
 | 
			
		||||
		Copyright:   "Ettore Di Giacinto",
 | 
			
		||||
@@ -362,7 +286,7 @@ Typically run during initrd to unlock all the LUKS partitions found
 | 
			
		||||
					&cli.StringFlag{},
 | 
			
		||||
				},
 | 
			
		||||
				Action: func(c *cli.Context) error {
 | 
			
		||||
					return unlockAll()
 | 
			
		||||
					return lib.UnlockAll()
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ type Bus struct {
 | 
			
		||||
 | 
			
		||||
func (b *Bus) LoadProviders() {
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	b.Manager.Autoload("kcrypt-discovery", "/system/discovery", "/oem/kcrypt", "/oem/system/discovery", wd).Register()
 | 
			
		||||
	b.Manager.Autoload("kcrypt-discovery", "/sysroot/system/discovery", "/system/discovery", "/oem/kcrypt", "/oem/system/discovery", wd).Register()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bus) Initialize() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										143
									
								
								pkg/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								pkg/config/config.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
// package config contains all the logic around kcrypt config
 | 
			
		||||
// This config includes everything below `kcrypt:` in the kairos config yaml
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/gofrs/uuid"
 | 
			
		||||
	"github.com/jaypipes/ghw/pkg/block"
 | 
			
		||||
	"github.com/kairos-io/kairos/pkg/config/collector"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"gopkg.in/yaml.v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// There are the directories under which we expect to find kairos configuration.
 | 
			
		||||
// When we are booted from an iso (during installation), configuration is expected
 | 
			
		||||
// under `/oem`. When we are booting an installed system (in initramfs phase),
 | 
			
		||||
// the path is `/sysroot/oem`.
 | 
			
		||||
var ConfigScanDirs = []string{"/oem", "/sysroot/oem"}
 | 
			
		||||
 | 
			
		||||
// This file is "hardcoded" to `/oem` because we only use this at install time
 | 
			
		||||
// in which case the config is in `/oem`.
 | 
			
		||||
var MappingsFile = "/oem/91-kcrypt-mappings.yaml"
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Kcrypt struct {
 | 
			
		||||
		UUIDLabelMappings map[string]string `yaml:"uuid_label_mappings,omitempty"`
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func PartitionToString(p *block.Partition) string {
 | 
			
		||||
	return fmt.Sprintf("%s:%s:%s", p.FilesystemLabel, p.Name, p.UUID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Takes a partition info string (as returned by PartitionToString) and return
 | 
			
		||||
// the partition label and the UUID
 | 
			
		||||
func partitionDataFromString(partitionStr string) (string, string, error) {
 | 
			
		||||
	parts := strings.Split(partitionStr, ":")
 | 
			
		||||
	if len(parts) != 3 {
 | 
			
		||||
		return "", "", errors.New("partition string not valid")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[2]), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetConfiguration(configDirs []string) (Config, error) {
 | 
			
		||||
	var result Config
 | 
			
		||||
 | 
			
		||||
	o := &collector.Options{}
 | 
			
		||||
 | 
			
		||||
	if err := o.Apply(collector.Directories(configDirs...), collector.NoLogs); err != nil {
 | 
			
		||||
		return result, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c, err := collector.Scan(o)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return result, err
 | 
			
		||||
	}
 | 
			
		||||
	configStr, err := c.String()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return result, err
 | 
			
		||||
	}
 | 
			
		||||
	if err = yaml.Unmarshal([]byte(configStr), &result); err != nil {
 | 
			
		||||
		return result, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetMapping updates the Config with partition information for
 | 
			
		||||
// one partition. This doesn't persist on the file. WriteMappings needs to
 | 
			
		||||
// be called after all mapping are in the Config (possibly with multiple calls
 | 
			
		||||
// to this function).
 | 
			
		||||
func (c *Config) SetMapping(partitionInfo string) error {
 | 
			
		||||
	label, uuid, err := partitionDataFromString(partitionInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// Initialize map
 | 
			
		||||
	if c.Kcrypt.UUIDLabelMappings == nil {
 | 
			
		||||
		c.Kcrypt.UUIDLabelMappings = map[string]string{}
 | 
			
		||||
	}
 | 
			
		||||
	c.Kcrypt.UUIDLabelMappings[label] = uuid
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WriteMappings will create or replace the MappingsFile
 | 
			
		||||
// It's called by kairos agent, at installation time, after the partitions
 | 
			
		||||
// have been created (and we have the UUIDs available).
 | 
			
		||||
func (c *Config) WriteMappings(fileName string) error {
 | 
			
		||||
	data, err := yaml.Marshal(&c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "marshalling the kcrypt configuration to yaml")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data = append([]byte(collector.DefaultHeader+"\n"), data...)
 | 
			
		||||
 | 
			
		||||
	err = os.WriteFile(fileName, data, 0744)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "writing the kcrypt configuration file")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Config) LookupUUIDForLabel(l string) string {
 | 
			
		||||
	return c.Kcrypt.UUIDLabelMappings[l]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Config) LookupLabelForUUID(uuid string) string {
 | 
			
		||||
	for k, v := range c.Kcrypt.UUIDLabelMappings {
 | 
			
		||||
		if v == uuid {
 | 
			
		||||
			return k
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLabelForUUID returns the partition label for a known UUID
 | 
			
		||||
// UUIDS are generated on luksify method
 | 
			
		||||
// They are generated by setting the namespace to DNS and the name to the fs label, so they are always the same
 | 
			
		||||
func (c Config) GetLabelForUUID(uuidCheck string) (string, error) {
 | 
			
		||||
	persistent := uuid.NewV5(uuid.NamespaceURL, "COS_PERSISTENT")
 | 
			
		||||
	oem := uuid.NewV5(uuid.NamespaceURL, "COS_OEM")
 | 
			
		||||
	fmt.Printf("Checking uuid: %s\n", uuidCheck)
 | 
			
		||||
	parsedUUID, err := uuid.FromString(uuidCheck)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	switch parsedUUID {
 | 
			
		||||
	case persistent:
 | 
			
		||||
		return "COS_PERSISTENT", nil
 | 
			
		||||
	case oem:
 | 
			
		||||
		return "COS_OEM", nil
 | 
			
		||||
	default:
 | 
			
		||||
		return "", errors.New("no partition found with that uuid")
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										216
									
								
								pkg/config/config_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								pkg/config/config_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
			
		||||
package config_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	. "github.com/onsi/ginkgo/v2"
 | 
			
		||||
	. "github.com/onsi/gomega"
 | 
			
		||||
 | 
			
		||||
	"github.com/kairos-io/kairos/pkg/config/collector"
 | 
			
		||||
	configpkg "github.com/kairos-io/kcrypt/pkg/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var _ = Describe("Config", func() {
 | 
			
		||||
	var tmpDir string
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	BeforeEach(func() {
 | 
			
		||||
		tmpDir, err = os.MkdirTemp("", "kcrypt-configuration-*")
 | 
			
		||||
		Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	AfterEach(func() {
 | 
			
		||||
		os.RemoveAll(tmpDir)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Describe("GetConfiguration", func() {
 | 
			
		||||
		When("the no relevant block exists", func() {
 | 
			
		||||
			It("returns empty Config", func() {
 | 
			
		||||
				c, err := configpkg.GetConfiguration([]string{tmpDir})
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				Expect(c.Kcrypt.UUIDLabelMappings).To(BeEmpty())
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		When("a kcrypt block exists", func() {
 | 
			
		||||
			var tmpFile *os.File
 | 
			
		||||
 | 
			
		||||
			BeforeEach(func() {
 | 
			
		||||
				tmpFile, err = os.CreateTemp(tmpDir, "config-*.yaml")
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				data := []byte(`#cloud-config
 | 
			
		||||
kcrypt:
 | 
			
		||||
  uuid_label_mappings:
 | 
			
		||||
    COS_PERSISTENT: some_uuid_here
 | 
			
		||||
`)
 | 
			
		||||
				err := os.WriteFile(tmpFile.Name(), data, 0744)
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			It("returns the Config", func() {
 | 
			
		||||
				c, err := configpkg.GetConfiguration([]string{tmpDir})
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				Expect(c.Kcrypt.UUIDLabelMappings["COS_PERSISTENT"]).To(Equal("some_uuid_here"))
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		When("multiple kcrypt block exist", func() {
 | 
			
		||||
			var tmpFile1, tmpFile2 *os.File
 | 
			
		||||
 | 
			
		||||
			BeforeEach(func() {
 | 
			
		||||
				tmpFile1, err = os.CreateTemp(tmpDir, "config-*.yaml")
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				data := []byte(`#cloud-config
 | 
			
		||||
kcrypt:
 | 
			
		||||
  challenger_server: http://test.org:8082
 | 
			
		||||
`)
 | 
			
		||||
				err := os.WriteFile(tmpFile1.Name(), data, 0744)
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
 | 
			
		||||
				tmpFile2, err = os.CreateTemp(tmpDir, "config-*.yaml")
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				data = []byte(`#cloud-config
 | 
			
		||||
kcrypt:
 | 
			
		||||
  uuid_label_mappings:
 | 
			
		||||
    COS_PERSISTENT: some_uuid_here
 | 
			
		||||
`)
 | 
			
		||||
				err = os.WriteFile(tmpFile2.Name(), data, 0744)
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			It("returns the merged Config", func() {
 | 
			
		||||
				c, err := configpkg.GetConfiguration([]string{tmpDir})
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				Expect(c.Kcrypt.UUIDLabelMappings["COS_PERSISTENT"]).To(Equal("some_uuid_here"))
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Describe("SetMapping", func() {
 | 
			
		||||
		var c configpkg.Config
 | 
			
		||||
 | 
			
		||||
		BeforeEach(func() {
 | 
			
		||||
			c, err = configpkg.GetConfiguration([]string{tmpDir})
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("adds partition information when empty and appends when not", func() {
 | 
			
		||||
			Expect(c.Kcrypt.UUIDLabelMappings).To(BeNil())
 | 
			
		||||
			err := c.SetMapping("some_label:some_name:some_uuid")
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			Expect(c.Kcrypt.UUIDLabelMappings["some_label"]).To(Equal("some_uuid"))
 | 
			
		||||
 | 
			
		||||
			err = c.SetMapping("some_other_label:some_name:some_other_uuid")
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			Expect(c.Kcrypt.UUIDLabelMappings["some_label"]).To(Equal("some_uuid"))
 | 
			
		||||
			Expect(c.Kcrypt.UUIDLabelMappings["some_other_label"]).To(Equal("some_other_uuid"))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Describe("WriteMappings", func() {
 | 
			
		||||
		var tmpFile *os.File
 | 
			
		||||
		var c configpkg.Config
 | 
			
		||||
 | 
			
		||||
		When("mappings config file already exists", func() {
 | 
			
		||||
			BeforeEach(func() {
 | 
			
		||||
				tmpFile, err = os.CreateTemp(tmpDir, "config-*.yaml")
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				data := []byte(`kcrypt:
 | 
			
		||||
  uuid_label_mappings:
 | 
			
		||||
    COS_PERSISTENT: some_uuid_here
 | 
			
		||||
`)
 | 
			
		||||
				err := os.WriteFile(tmpFile.Name(), data, 0744)
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			It("replaces the file contents", func() {
 | 
			
		||||
				err := c.SetMapping("COS_PERSISTENT:the_new_name:the_new_uuid")
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				err = c.WriteMappings(tmpFile.Name())
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				data, err := os.ReadFile(tmpFile.Name())
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				Expect(collector.HasValidHeader(string(data))).To(BeTrue())
 | 
			
		||||
 | 
			
		||||
				newConfig, err := configpkg.GetConfiguration([]string{tmpDir})
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				Expect(newConfig.Kcrypt.UUIDLabelMappings["COS_PERSISTENT"]).To(Equal("the_new_uuid"))
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		When("a mappings configuration file doesn't exist", func() {
 | 
			
		||||
			BeforeEach(func() {
 | 
			
		||||
				tmpFile, err = os.CreateTemp(tmpDir, "config-*.yaml")
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				// We will reuse the same name but we make sure the file doesn't exist.
 | 
			
		||||
				os.RemoveAll(tmpFile.Name())
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			It("creates the file with the given mappings", func() {
 | 
			
		||||
				err := c.SetMapping("COS_PERSISTENT:the_new_name:the_new_uuid")
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				err = c.WriteMappings(tmpFile.Name())
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
 | 
			
		||||
				newConfig, err := configpkg.GetConfiguration([]string{tmpDir})
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				Expect(newConfig.Kcrypt.UUIDLabelMappings["COS_PERSISTENT"]).To(Equal("the_new_uuid"))
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Describe("LookupUUIDForLabel", func() {
 | 
			
		||||
		var tmpFile *os.File
 | 
			
		||||
		var c configpkg.Config
 | 
			
		||||
 | 
			
		||||
		BeforeEach(func() {
 | 
			
		||||
			tmpFile, err = os.CreateTemp(tmpDir, "config-*.yaml")
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			// Should trim the whitespace
 | 
			
		||||
			err = c.SetMapping("COS_PERSISTENT:the_new_name:some_uuid_1\n")
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			err = c.WriteMappings(tmpFile.Name())
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("returns the correct UUID", func() {
 | 
			
		||||
			uuid := c.LookupUUIDForLabel("COS_PERSISTENT")
 | 
			
		||||
			Expect(uuid).To(Equal("some_uuid_1"))
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("returns an empty UUID when the label is not found", func() {
 | 
			
		||||
			uuid := c.LookupUUIDForLabel("DOESNT_EXIST")
 | 
			
		||||
			Expect(uuid).To(Equal(""))
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("returns an empty UUID when the UUIDLabelMappings is nil", func() {
 | 
			
		||||
			c.Kcrypt.UUIDLabelMappings = nil
 | 
			
		||||
			uuid := c.LookupUUIDForLabel("COS_PERSISTENT")
 | 
			
		||||
			Expect(uuid).To(Equal(""))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Describe("LookupLabelForUUID", func() {
 | 
			
		||||
		var tmpFile *os.File
 | 
			
		||||
		var c configpkg.Config
 | 
			
		||||
 | 
			
		||||
		BeforeEach(func() {
 | 
			
		||||
			tmpFile, err = os.CreateTemp(tmpDir, "config-*.yaml")
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			err = c.SetMapping("COS_PERSISTENT:the_new_name:some_uuid_1")
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			err = c.WriteMappings(tmpFile.Name())
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("returns the correct label", func() {
 | 
			
		||||
			uuid := c.LookupLabelForUUID("some_uuid_1")
 | 
			
		||||
			Expect(uuid).To(Equal("COS_PERSISTENT"))
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("returns an empty label when UUID doesn't exist", func() {
 | 
			
		||||
			uuid := c.LookupLabelForUUID("doesnt_exist")
 | 
			
		||||
			Expect(uuid).To(Equal(""))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
})
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package partition_info
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
@@ -9,5 +9,5 @@ import (
 | 
			
		||||
 | 
			
		||||
func TestPartitionINfo(t *testing.T) {
 | 
			
		||||
	RegisterFailHandler(Fail)
 | 
			
		||||
	RunSpecs(t, "PartitionInfo file parser test suite")
 | 
			
		||||
	RunSpecs(t, "Kcrypt config test suite")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								pkg/lib/unlock.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								pkg/lib/unlock.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
package lib
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/anatol/luks.go"
 | 
			
		||||
	"github.com/jaypipes/ghw"
 | 
			
		||||
	"github.com/jaypipes/ghw/pkg/block"
 | 
			
		||||
	"github.com/kairos-io/kairos-sdk/utils"
 | 
			
		||||
	"github.com/kairos-io/kcrypt/pkg/bus"
 | 
			
		||||
	configpkg "github.com/kairos-io/kcrypt/pkg/config"
 | 
			
		||||
	"github.com/mudler/go-pluggable"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UnlockAll Unlocks all encrypted devices found in the system
 | 
			
		||||
func UnlockAll() error {
 | 
			
		||||
	bus.Manager.Initialize()
 | 
			
		||||
 | 
			
		||||
	config, err := configpkg.GetConfiguration(configpkg.ConfigScanDirs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Printf("Warning: Could not read kcrypt configuration '%s'\n", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	blk, err := ghw.Block()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Printf("Warning: Error reading partitions '%s \n", err.Error())
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, disk := range blk.Disks {
 | 
			
		||||
		for _, p := range disk.Partitions {
 | 
			
		||||
			if p.Type == "crypto_LUKS" {
 | 
			
		||||
				// Get the luks UUID directly from cryptsetup
 | 
			
		||||
				volumeUUID, err := utils.SH(fmt.Sprintf("cryptsetup luksUUID %s", filepath.Join("/dev", p.Name)))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				volumeUUID = strings.TrimSpace(volumeUUID)
 | 
			
		||||
				if volumeUUID == "" {
 | 
			
		||||
					fmt.Printf("No uuid for %s, skipping\n", p.Name)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				p.Label, err = config.GetLabelForUUID(volumeUUID)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				// Check if device is already mounted
 | 
			
		||||
				// We mount it under /dev/mapper/DEVICE, so It's pretty easy to check
 | 
			
		||||
				if !utils.Exists(filepath.Join("/dev", "mapper", p.Name)) {
 | 
			
		||||
					fmt.Printf("Unmounted Luks found at '%s' LABEL '%s' \n", filepath.Join("/dev", p.Name), p.Label)
 | 
			
		||||
					err = UnlockDisk(p)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						fmt.Printf("Unlocking failed: '%s'\n", err.Error())
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					fmt.Printf("Device %s seems to be mounted at %s, skipping\n", filepath.Join("/dev", p.Name), filepath.Join("/dev", "mapper", p.Name))
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnlockDisk unlocks a single block.Partition
 | 
			
		||||
func UnlockDisk(b *block.Partition) error {
 | 
			
		||||
	pass, err := GetPassword(b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error retreiving password remotely: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return LuksUnlock(filepath.Join("/dev", b.Name), b.Name, pass)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPassword gets the password for a block.Partition
 | 
			
		||||
// TODO: Ask to discovery a pass to unlock. keep waiting until we get it and a timeout is exhausted with retrials (exp backoff)
 | 
			
		||||
func GetPassword(b *block.Partition) (password string, err error) {
 | 
			
		||||
	bus.Reload()
 | 
			
		||||
 | 
			
		||||
	bus.Manager.Response(bus.EventDiscoveryPassword, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
 | 
			
		||||
		password = r.Data
 | 
			
		||||
		if r.Errored() {
 | 
			
		||||
			err = fmt.Errorf("failed discovery: %s", r.Error)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	_, err = bus.Manager.Publish(bus.EventDiscoveryPassword, b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return password, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if password == "" {
 | 
			
		||||
		return password, fmt.Errorf("received empty password")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LuksUnlock(device, mapper, password string) error {
 | 
			
		||||
	dev, err := luks.Open(device)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// handle error
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer dev.Close()
 | 
			
		||||
 | 
			
		||||
	err = dev.Unlock(0, []byte(password), mapper)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,125 +0,0 @@
 | 
			
		||||
package partition_info
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/jaypipes/ghw/pkg/block"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"gopkg.in/yaml.v3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const DefaultPartitionInfoFile = "/oem/partition_info.yaml"
 | 
			
		||||
 | 
			
		||||
// PartitionInfo maps a partition label to a partition UUID.
 | 
			
		||||
// It's used in order to be able to ask the kcrypt-challenger for the passphrase
 | 
			
		||||
// using the partition label, even when the label is not accessible (e.g. before
 | 
			
		||||
// decrypting the partition). The UUID can be used to lookup the partition label
 | 
			
		||||
// and make the request.
 | 
			
		||||
type PartitionInfo struct {
 | 
			
		||||
	file    string
 | 
			
		||||
	mapping map[string]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewPartitionInfoFromFile reads the given partition info file (if one exists)
 | 
			
		||||
// and returns a pointer to a PartitionInfo object.
 | 
			
		||||
// If a file doesn't exist, the function will create one and return an "empty"
 | 
			
		||||
// PartitionInfo object.
 | 
			
		||||
// The boolean return value indicates whether a file existed or not (true means,
 | 
			
		||||
// a file existed already).
 | 
			
		||||
func NewPartitionInfoFromFile(file string) (*PartitionInfo, bool, error) {
 | 
			
		||||
	existed, err := createInfoFileIfNotExists(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, existed, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mapping, err := ParsePartitionInfoFile(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, existed, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &PartitionInfo{
 | 
			
		||||
		file:    file,
 | 
			
		||||
		mapping: mapping,
 | 
			
		||||
	}, existed, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pi PartitionInfo) LookupUUIDForLabel(l string) string {
 | 
			
		||||
	return pi.mapping[l]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pi PartitionInfo) LookupLabelForUUID(uuid string) string {
 | 
			
		||||
	for k, v := range pi.mapping {
 | 
			
		||||
		if v == uuid {
 | 
			
		||||
			return k
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdatePartitionLabelMapping takes partition information as a string argument
 | 
			
		||||
// the the form: `label:name:uuid` (that's what the `kcrypt encrypt` command returns
 | 
			
		||||
// on success. This function stores it in the PartitionInfoFile yaml file for
 | 
			
		||||
// later use.
 | 
			
		||||
func (pi PartitionInfo) UpdateMapping(partitionData string) error {
 | 
			
		||||
	label, uuid := PartitionDataFromString(partitionData)
 | 
			
		||||
	pi.mapping[label] = uuid
 | 
			
		||||
 | 
			
		||||
	return pi.save()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pi PartitionInfo) save() error {
 | 
			
		||||
	data, err := yaml.Marshal(&pi.mapping)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "marshalling the new partition info to yaml")
 | 
			
		||||
	}
 | 
			
		||||
	err = ioutil.WriteFile(pi.file, data, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "writing back the partition info file")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func PartitionToString(p *block.Partition) string {
 | 
			
		||||
	return fmt.Sprintf("%s:%s:%s", p.Label, p.Name, p.UUID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Takes a partition info string (as returned by PartitionToString) and return
 | 
			
		||||
// the partition label and the UUID
 | 
			
		||||
func PartitionDataFromString(partitionStr string) (string, string) {
 | 
			
		||||
	parts := strings.Split(partitionStr, ":")
 | 
			
		||||
 | 
			
		||||
	return parts[0], parts[2]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParsePartitionInfoFile(file string) (map[string]string, error) {
 | 
			
		||||
	var result map[string]string
 | 
			
		||||
 | 
			
		||||
	yamlFile, err := ioutil.ReadFile(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return result, errors.Wrap(err, "reading the partition info file")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = yaml.Unmarshal(yamlFile, &result)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return result, errors.Wrap(err, "unmarshalling partition info file")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// createInfoFileIfNotExists returns true if file already exists or creates the
 | 
			
		||||
// the file if it doesn't exist and returns false.
 | 
			
		||||
func createInfoFileIfNotExists(fileName string) (bool, error) {
 | 
			
		||||
	_, err := os.Stat(fileName)
 | 
			
		||||
	if errors.Is(err, os.ErrNotExist) {
 | 
			
		||||
		if _, err := os.Create(fileName); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,179 +0,0 @@
 | 
			
		||||
package partition_info_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/jaypipes/ghw/pkg/block"
 | 
			
		||||
	. "github.com/onsi/ginkgo/v2"
 | 
			
		||||
	. "github.com/onsi/gomega"
 | 
			
		||||
 | 
			
		||||
	pi "github.com/kairos-io/kcrypt/pkg/partition_info"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var _ = Describe("Partition Info file parsing", func() {
 | 
			
		||||
	Describe("NewPartitionInfoFromFile", func() {
 | 
			
		||||
		var file string
 | 
			
		||||
 | 
			
		||||
		BeforeEach(func() {
 | 
			
		||||
			file = "../../tests/assets/partition_info.yaml"
 | 
			
		||||
		})
 | 
			
		||||
		When("the files exists already", func() {
 | 
			
		||||
			It("returns 'true' and a PartitionInfo object", func() {
 | 
			
		||||
				result, existed, err := pi.NewPartitionInfoFromFile(file)
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				Expect(result).ToNot(BeNil())
 | 
			
		||||
				Expect(existed).To(BeTrue())
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		When("a file doesn't exist", func() {
 | 
			
		||||
			var fileName string
 | 
			
		||||
			BeforeEach(func() {
 | 
			
		||||
				fileName = path.Join(
 | 
			
		||||
					os.TempDir(),
 | 
			
		||||
					fmt.Sprintf("partition-info-%d.yaml", time.Now().UnixNano()))
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			It("creates the file and returns 'false' and an (empty) mapping", func() {
 | 
			
		||||
				result, existed, err := pi.NewPartitionInfoFromFile(fileName)
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
				Expect(result).ToNot(BeNil())
 | 
			
		||||
				Expect(existed).To(BeFalse())
 | 
			
		||||
				_, err = os.Stat(fileName)
 | 
			
		||||
				Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Describe("ParsePartitionInfoFile", func() {
 | 
			
		||||
		var file string
 | 
			
		||||
 | 
			
		||||
		BeforeEach(func() {
 | 
			
		||||
			file = "../../tests/assets/partition_info.yaml"
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("parses the file correctly", func() {
 | 
			
		||||
			info, err := pi.ParsePartitionInfoFile(file)
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			Expect(len(info)).To(Equal(2))
 | 
			
		||||
			Expect(info["COS_PERSISTENT"]).To(Equal("some_uuid_1"))
 | 
			
		||||
			Expect(info["COS_OTHER"]).To(Equal("some_uuid_2"))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Describe("PartitionToString", func() {
 | 
			
		||||
		var partition *block.Partition
 | 
			
		||||
 | 
			
		||||
		BeforeEach(func() {
 | 
			
		||||
			partition = &block.Partition{
 | 
			
		||||
				Disk:       nil,
 | 
			
		||||
				Name:       "sda1",
 | 
			
		||||
				Label:      "COS_PERSISTENT",
 | 
			
		||||
				MountPoint: "/mnt/sda1",
 | 
			
		||||
				UUID:       "some_uuid_here",
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("returns a string representation of the partition data", func() {
 | 
			
		||||
			Expect(pi.PartitionToString(partition)).To(Equal("COS_PERSISTENT:sda1:some_uuid_here"))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Describe("PartitionDataFromString", func() {
 | 
			
		||||
		var partitionData string
 | 
			
		||||
 | 
			
		||||
		BeforeEach(func() {
 | 
			
		||||
			partitionData = "THE_LABEL:the_name:the_uuid"
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("returns the label and the uuid", func() {
 | 
			
		||||
			label, uuid := pi.PartitionDataFromString(partitionData)
 | 
			
		||||
			Expect(label).To(Equal("THE_LABEL"))
 | 
			
		||||
			Expect(uuid).To(Equal("the_uuid"))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Describe("UpdateMapping", func() {
 | 
			
		||||
		var file *os.File
 | 
			
		||||
		var err error
 | 
			
		||||
		var partitionInfo *pi.PartitionInfo
 | 
			
		||||
 | 
			
		||||
		BeforeEach(func() {
 | 
			
		||||
			file, err = ioutil.TempFile("", "partition-info.*.yaml")
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
 | 
			
		||||
			_, err = file.Write([]byte("TO_KEEP: old_uuid_1"))
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
 | 
			
		||||
			partitionInfo, _, err = pi.NewPartitionInfoFromFile(file.Name())
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		AfterEach(func() {
 | 
			
		||||
			os.Remove(file.Name())
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("Updates the file correctly from a `kcrypt encrypt` return value", func() {
 | 
			
		||||
			partitionData := "TO_BE_ADDED:some_name:new_uuid"
 | 
			
		||||
 | 
			
		||||
			err = partitionInfo.UpdateMapping(partitionData)
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
 | 
			
		||||
			dat, err := os.ReadFile(file.Name())
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
 | 
			
		||||
			expectedContent := `TO_BE_ADDED: new_uuid
 | 
			
		||||
TO_KEEP: old_uuid_1
 | 
			
		||||
`
 | 
			
		||||
			Expect(string(dat)).To(Equal(expectedContent))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Describe("LookupUUIDForLabel", func() {
 | 
			
		||||
		var file string
 | 
			
		||||
		var partitionInfo *pi.PartitionInfo
 | 
			
		||||
		var err error
 | 
			
		||||
 | 
			
		||||
		BeforeEach(func() {
 | 
			
		||||
			file = "../../tests/assets/partition_info.yaml"
 | 
			
		||||
			partitionInfo, _, err = pi.NewPartitionInfoFromFile(file)
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("returns the correct UUID", func() {
 | 
			
		||||
			uuid := partitionInfo.LookupUUIDForLabel("COS_PERSISTENT")
 | 
			
		||||
			Expect(uuid).To(Equal("some_uuid_1"))
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("returns an empty UUID when the label is not found", func() {
 | 
			
		||||
			uuid := partitionInfo.LookupUUIDForLabel("DOESNT_EXIST")
 | 
			
		||||
			Expect(uuid).To(Equal(""))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Describe("LookupLabelForUUID", func() {
 | 
			
		||||
		var file string
 | 
			
		||||
		var partitionInfo *pi.PartitionInfo
 | 
			
		||||
		var err error
 | 
			
		||||
 | 
			
		||||
		BeforeEach(func() {
 | 
			
		||||
			file = "../../tests/assets/partition_info.yaml"
 | 
			
		||||
			partitionInfo, _, err = pi.NewPartitionInfoFromFile(file)
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("returns the correct label", func() {
 | 
			
		||||
			uuid := partitionInfo.LookupLabelForUUID("some_uuid_1")
 | 
			
		||||
			Expect(uuid).To(Equal("COS_PERSISTENT"))
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("returns an empty label when UUID doesn't exist", func() {
 | 
			
		||||
			uuid := partitionInfo.LookupLabelForUUID("doesnt_exist")
 | 
			
		||||
			Expect(uuid).To(Equal(""))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
})
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
COS_PERSISTENT: some_uuid_1
 | 
			
		||||
COS_OTHER: some_uuid_2
 | 
			
		||||
		Reference in New Issue
	
	Block a user