Implement monorepo layout
Implemented the nexantic monorepo.
Smalltown code was moved to `core`. From now on all code will live in top level directories named after the projects with the exception for general purpose libraries which should go to `<lang>libs`.
General build and utility folders are underscore prefixed.
The repo name will from now on be rNXT (nexantic). I think this change makes sense since components in this repo will not all be part of Smalltown, the Smalltown brand has been claimed by Signon GmbH so we need to change it anyway and the longer we wait the harder it will be to change/move it.
Test Plan: Launched Smalltown using `./scripts/bin/bazel run //core/scripts:launch`
X-Origin-Diff: phab/D210
GitOrigin-RevId: fa5a7f08143d2ead2cb7206b4c63ab641794162c
diff --git a/BUILD b/BUILD
index 5955dd2..fbd3a0c 100644
--- a/BUILD
+++ b/BUILD
@@ -1,60 +1,5 @@
load("@bazel_gazelle//:def.bzl", "gazelle")
-# gazelle:prefix git.monogon.dev/source/smalltown.git
-# gazelle:exclude generated
+# gazelle:prefix git.monogon.dev/source/nexantic.git
+# gazelle:exclude core/generated
gazelle(name = "gazelle")
-
-genrule(
- name = "image",
- srcs = [
- "@//cmd/mkimage",
- "@//build/linux_kernel:image",
- ],
- outs = [
- "smalltown.img",
- ],
- cmd = """
- $(location @//cmd/mkimage) $(location @//build/linux_kernel:image) $@
- """,
- visibility = ["//visibility:public"],
-)
-
-genrule(
- name = "swtpm_data",
- outs = [
- "tpm/tpm2-00.permall",
- "tpm/signkey.pem",
- "tpm/issuercert.pem",
- ],
- cmd = """
- mkdir -p tpm/ca
-
- cat <<EOF > tpm/swtpm.conf
-create_certs_tool= /usr/share/swtpm/swtpm-localca
-create_certs_tool_config = tpm/swtpm-localca.conf
-create_certs_tool_options = /etc/swtpm-localca.options
-EOF
-
- cat <<EOF > tpm/swtpm-localca.conf
-statedir = tpm/ca
-signingkey = tpm/ca/signkey.pem
-issuercert = tpm/ca/issuercert.pem
-certserial = tpm/ca/certserial
-EOF
-
- swtpm_setup \
- --tpmstate tpm \
- --create-ek-cert \
- --create-platform-cert \
- --allow-signing \
- --tpm2 \
- --display \
- --pcr-banks sha1,sha256,sha384,sha512 \
- --config tpm/swtpm.conf
-
- cp tpm/tpm2-00.permall $(location tpm/tpm2-00.permall)
- cp tpm/ca/issuercert.pem $(location tpm/issuercert.pem)
- cp tpm/ca/signkey.pem $(location tpm/signkey.pem)
- """,
- visibility = ["//visibility:public"],
-)
diff --git a/README.md b/README.md
index 303b602..6aadcd7 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,21 @@
-# Smalltown Operating System
+# Nexantic monorepo
-## Run build
+This is the monorepo storing all of nexantic's internal projects and libraries.
-The build uses a Fedora 30 base image with a set of dependencies.
-Guide has been tested on a Fedora 30 host, with latest rW deployed.
+## Environment
-Build the base image:
+All builds should be executed using the shipped `nexantic-dev` container which is automatically built by the create
+script.
-```
-podman build -t smalltown-builder .
-```
+The container contains all necessary dependencies and env configurations necessary to get started right away.
-Launch the VM:
+#### Usage
-```
-scripts/bin/bazel run scripts:launch
-```
+Spinning up: `scripts/create_container.sh`
-Exit qemu using the monitor console: `Ctrl-A c quit`.
+Spinning down: `scripts/destroy_container.sh`
+
+Running commands: `scripts/run_in_container.sh @`
+
+Using bazel: `scripts/bin/bazel @`
+
diff --git a/WORKSPACE b/WORKSPACE
index 4305109..9a8dd93 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,4 +1,4 @@
-workspace(name = "smalltown")
+workspace(name = "nexantic")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
@@ -107,7 +107,7 @@
# edk2-stable201908
new_git_repository(
name = "edk2",
- build_file = "@//build/edk2:BUILD",
+ build_file = "@//core/build/edk2:BUILD",
commit = "37eef91017ad042035090cae46557f9d6e2d5917",
init_submodules = True,
remote = "https://github.com/tianocore/edk2",
@@ -147,7 +147,7 @@
build_file_content = all_content,
patch_args = ["-p1"],
patches = [
- "@//build/utils/xfsprogs_dev:0001-Fixes-for-static-compilation.patch",
+ "@//core/build/utils/xfsprogs_dev:0001-Fixes-for-static-compilation.patch",
],
sha256 = "6187f25f1744d1ecbb028b0ea210ad586d0f2dae24e258e4688c67740cc861ef",
strip_prefix = "xfsprogs-dev-" + xfsprogs_dev_version,
diff --git a/build/Dockerfile b/build/Dockerfile
new file mode 100644
index 0000000..37d59cd
--- /dev/null
+++ b/build/Dockerfile
@@ -0,0 +1,47 @@
+FROM fedora:30
+
+RUN dnf -y upgrade && \
+ dnf -y install \
+ "@Development Tools" \
+ g++ \
+ libuuid-devel \
+ python3 \
+ nasm \
+ acpica-tools \
+ gettext-devel \
+ autoconf \
+ bison \
+ libtool \
+ automake \
+ flex \
+ glibc-static \
+ elfutils-libelf-devel \
+ libblkid-devel \
+ lz4 \
+ bc \
+ hostname \
+ which \
+ swtpm-tools \
+ rsync \
+ qemu-system-x86-core
+
+# Workaround for a binutils bugs in F30, which generates invalid ELF binaries
+# when linking statically with musl.
+RUN dnf -y install fedora-repos-rawhide && \
+ dnf -y --disablerepo=* --enablerepo=rawhide --releasever=32 upgrade binutils
+
+# Install Bazel binary
+RUN curl -o /usr/local/bin/bazel \
+ https://releases.bazel.build/1.1.0/release/bazel-1.1.0-linux-x86_64 && \
+ echo 'f54ab5f31b8d7c6a0ce9dee387af45b1d6577ff9625ef6c535896b59cdf8828a /usr/local/bin/bazel' | sha256sum --check && \
+ chmod +x /usr/local/bin/bazel
+
+# Use a shared Go module cache for gazelle
+# https://github.com/bazelbuild/bazel-gazelle/pull/535
+ENV GO_REPOSITORY_USE_HOST_CACHE=1
+
+# --userns=keep-id uses the workdir as $HOME otherwise
+RUN mkdir /user
+ENV HOME=/user
+
+WORKDIR /work
diff --git a/core/BUILD b/core/BUILD
new file mode 100644
index 0000000..d9ed017
--- /dev/null
+++ b/core/BUILD
@@ -0,0 +1,54 @@
+genrule(
+ name = "image",
+ srcs = [
+ "@//core/cmd/mkimage",
+ "@//core/build/linux_kernel:image",
+ ],
+ outs = [
+ "smalltown.img",
+ ],
+ cmd = """
+ $(location @//core/cmd/mkimage) $(location @//core/build/linux_kernel:image) $@
+ """,
+ visibility = ["//visibility:public"],
+)
+
+genrule(
+ name = "swtpm_data",
+ outs = [
+ "tpm/tpm2-00.permall",
+ "tpm/signkey.pem",
+ "tpm/issuercert.pem",
+ ],
+ cmd = """
+ mkdir -p tpm/ca
+
+ cat <<EOF > tpm/swtpm.conf
+create_certs_tool= /usr/share/swtpm/swtpm-localca
+create_certs_tool_config = tpm/swtpm-localca.conf
+create_certs_tool_options = /etc/swtpm-localca.options
+EOF
+
+ cat <<EOF > tpm/swtpm-localca.conf
+statedir = tpm/ca
+signingkey = tpm/ca/signkey.pem
+issuercert = tpm/ca/issuercert.pem
+certserial = tpm/ca/certserial
+EOF
+
+ swtpm_setup \
+ --tpmstate tpm \
+ --create-ek-cert \
+ --create-platform-cert \
+ --allow-signing \
+ --tpm2 \
+ --display \
+ --pcr-banks sha1,sha256,sha384,sha512 \
+ --config tpm/swtpm.conf
+
+ cp tpm/tpm2-00.permall $(location tpm/tpm2-00.permall)
+ cp tpm/ca/issuercert.pem $(location tpm/issuercert.pem)
+ cp tpm/ca/signkey.pem $(location tpm/signkey.pem)
+ """,
+ visibility = ["//visibility:public"],
+)
diff --git a/core/README.md b/core/README.md
new file mode 100644
index 0000000..48dd6a0
--- /dev/null
+++ b/core/README.md
@@ -0,0 +1,14 @@
+# Smalltown Operating System
+
+## Run build
+
+The build uses a Fedora 30 base image with a set of dependencies.
+Guide has been tested on a Fedora 30 host, with latest rW deployed.
+
+Launch the VM:
+
+```
+scripts/bin/bazel run //core/scripts:launch
+```
+
+Exit qemu using the monitor console: `Ctrl-A c quit`.
diff --git a/core/api/api/BUILD.bazel b/core/api/api/BUILD.bazel
new file mode 100644
index 0000000..d28e60c
--- /dev/null
+++ b/core/api/api/BUILD.bazel
@@ -0,0 +1,25 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
+
+proto_library(
+ name = "api_proto",
+ srcs = ["schema.proto"],
+ visibility = ["//visibility:public"],
+ deps = ["//core/api/common:common_proto"],
+)
+
+go_proto_library(
+ name = "api_go_proto",
+ compilers = ["@io_bazel_rules_go//proto:go_grpc"],
+ importpath = "git.monogon.dev/source/nexantic.git/core/generated/api",
+ proto = ":api_proto",
+ visibility = ["//visibility:public"],
+ deps = ["//core/api/common:go_default_library"],
+)
+
+go_library(
+ name = "go_default_library",
+ embed = [":api_go_proto"],
+ importpath = "git.monogon.dev/source/nexantic.git/core/generated/api",
+ visibility = ["//visibility:public"],
+)
diff --git a/core/api/api/schema.proto b/core/api/api/schema.proto
new file mode 100644
index 0000000..d6721a0
--- /dev/null
+++ b/core/api/api/schema.proto
@@ -0,0 +1,158 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+package api;
+
+import "core/api/common/main.proto";
+
+option go_package = "git.monogon.dev/source/nexantic.git/core/generated/api";
+
+service ClusterManagement {
+ // Add a node to the Smalltown cluster
+ rpc AddNode (AddNodeRequest) returns (AddNodeResponse) {
+
+ }
+
+ // Remove a node from the Smalltown cluster
+ rpc RemoveNode (RemoveNodeRequest) returns (RemoveNodeRequest) {
+
+ }
+
+ // Get all cluster nodes
+ rpc GetNodes (GetNodesRequest) returns (GetNodesResponse) {
+
+ }
+}
+
+service SetupService {
+ // SetupNewCluster configures this node to either start a new Smalltown cluster or join an existing one
+ rpc Setup (SetupRequest) returns (SetupResponse) {
+
+ }
+
+ // JoinCluster can be called by another Smalltown node when the node has been put in to JOIN_CLUSTER mode using Setup.
+ // This request sets up all necessary config variables, joins the consensus and puts the node in production state.
+ rpc ProvisionCluster (ProvisionClusterRequest) returns (ProvisionClusterResponse) {
+
+ }
+
+ rpc Attest (AttestRequest) returns (AttestResponse) {
+
+ }
+}
+
+message SetupRequest {
+ oneof request {
+ NewClusterRequest newCluster = 1;
+ JoinClusterRequest joinCluster = 2;
+ }
+
+}
+
+message NewClusterRequest {
+ string nodeName = 1;
+ string externalHost = 2;
+ smalltown.common.TrustBackend trustBackend = 3;
+}
+
+message JoinClusterRequest {
+}
+
+message SetupResponse {
+ oneof response {
+ NewClusterResponse newCluster = 1;
+ JoinClusterResponse joinCluster = 2;
+ }
+}
+
+message NewClusterResponse {
+}
+
+message JoinClusterResponse {
+ string provisioningToken = 1;
+}
+
+message ProvisionClusterRequest {
+ string provisioningToken = 1;
+
+ string initialCluster = 2;
+ string nodeName = 3;
+ string externalHost = 4;
+ smalltown.common.TrustBackend trustBackend = 5;
+ bytes storeKey = 6;
+}
+
+message ProvisionClusterResponse {
+
+}
+
+message AttestRequest {
+ string challenge = 1;
+}
+
+message AttestResponse {
+ string response = 1;
+}
+
+message Key {
+ string label = 1;
+ string type = 2;
+}
+
+message CreateKeyRequest {
+ Key key = 1;
+}
+
+message CreateKeyResponse {
+
+}
+
+message AddNodeRequest {
+ string host = 1;
+ uint32 apiPort = 2;
+ uint32 consensusPort = 3;
+ string token = 4;
+ string name = 5;
+ smalltown.common.TrustBackend trustBackend = 6;
+}
+
+message AddNodeResponse {
+
+}
+
+message RemoveNodeRequest {
+
+}
+
+message RemoveNodeResponse {
+
+}
+
+message GetNodesRequest {
+
+}
+
+message GetNodesResponse {
+ repeated Node nodes = 1;
+}
+
+message Node {
+ uint64 id = 1;
+ string name = 2;
+ string address = 3;
+ bool synced = 4;
+}
diff --git a/core/api/common/BUILD.bazel b/core/api/common/BUILD.bazel
new file mode 100644
index 0000000..30b3a27
--- /dev/null
+++ b/core/api/common/BUILD.bazel
@@ -0,0 +1,22 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
+
+proto_library(
+ name = "common_proto",
+ srcs = ["main.proto"],
+ visibility = ["//visibility:public"],
+)
+
+go_proto_library(
+ name = "common_go_proto",
+ importpath = "git.monogon.dev/source/nexantic.git/core/generated/common",
+ proto = ":common_proto",
+ visibility = ["//visibility:public"],
+)
+
+go_library(
+ name = "go_default_library",
+ embed = [":common_go_proto"],
+ importpath = "git.monogon.dev/source/nexantic.git/core/generated/common",
+ visibility = ["//visibility:public"],
+)
diff --git a/core/api/common/main.proto b/core/api/common/main.proto
new file mode 100644
index 0000000..8ce8f99
--- /dev/null
+++ b/core/api/common/main.proto
@@ -0,0 +1,30 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+option go_package = "git.monogon.dev/source/nexantic.git/core/generated/common";
+package smalltown.common;
+
+message KV {
+ string key = 1;
+ bytes value = 2;
+}
+
+enum TrustBackend {
+ DUMMY = 0;
+ TPM = 1;
+}
diff --git a/build/edk2/BUILD b/core/build/edk2/BUILD
similarity index 100%
rename from build/edk2/BUILD
rename to core/build/edk2/BUILD
diff --git a/build/linux_kernel/BUILD b/core/build/linux_kernel/BUILD
similarity index 75%
rename from build/linux_kernel/BUILD
rename to core/build/linux_kernel/BUILD
index b5665e2..e130142 100644
--- a/build/linux_kernel/BUILD
+++ b/core/build/linux_kernel/BUILD
@@ -2,8 +2,8 @@
name = "image",
srcs = [
"@linux_kernel//:all",
- "@//cmd/init",
- "@//build/utils",
+ "@//core/cmd/init",
+ "@//core/build/utils",
"initramfs.list",
"linux-smalltown.config",
],
@@ -16,8 +16,8 @@
mkdir $$DIR/.bin
cp $(location linux-smalltown.config) $$DIR/.config
- cp $(location @//cmd/init) $$DIR/.bin/init
- cp $(locations @//build/utils) $$DIR/.bin/
+ cp $(location @//core/cmd/init) $$DIR/.bin/init
+ cp $(locations @//core/build/utils) $$DIR/.bin/
cp $(location initramfs.list) $$DIR/initramfs.list
(cd $$DIR && make -j 16) >/dev/null
diff --git a/build/linux_kernel/initramfs.list b/core/build/linux_kernel/initramfs.list
similarity index 100%
rename from build/linux_kernel/initramfs.list
rename to core/build/linux_kernel/initramfs.list
diff --git a/build/linux_kernel/linux-signos.config b/core/build/linux_kernel/linux-signos.config
similarity index 100%
rename from build/linux_kernel/linux-signos.config
rename to core/build/linux_kernel/linux-signos.config
diff --git a/build/utils/BUILD b/core/build/utils/BUILD
similarity index 100%
rename from build/utils/BUILD
rename to core/build/utils/BUILD
diff --git a/build/utils/xfsprogs_dev/0001-Fixes-for-static-compilation.patch b/core/build/utils/xfsprogs_dev/0001-Fixes-for-static-compilation.patch
similarity index 100%
rename from build/utils/xfsprogs_dev/0001-Fixes-for-static-compilation.patch
rename to core/build/utils/xfsprogs_dev/0001-Fixes-for-static-compilation.patch
diff --git a/build/utils/xfsprogs_dev/BUILD b/core/build/utils/xfsprogs_dev/BUILD
similarity index 100%
rename from build/utils/xfsprogs_dev/BUILD
rename to core/build/utils/xfsprogs_dev/BUILD
diff --git a/cmd/init/BUILD.bazel b/core/cmd/init/BUILD.bazel
similarity index 66%
rename from cmd/init/BUILD.bazel
rename to core/cmd/init/BUILD.bazel
index 4246105..afe4a39 100644
--- a/cmd/init/BUILD.bazel
+++ b/core/cmd/init/BUILD.bazel
@@ -3,12 +3,12 @@
go_library(
name = "go_default_library",
srcs = ["main.go"],
- importpath = "git.monogon.dev/source/smalltown.git/cmd/init",
+ importpath = "git.monogon.dev/source/nexantic.git/core/cmd/init",
visibility = ["//visibility:private"],
deps = [
- "//internal/network:go_default_library",
- "//internal/node:go_default_library",
- "//pkg/tpm:go_default_library",
+ "//core/internal/network:go_default_library",
+ "//core/internal/node:go_default_library",
+ "//core/pkg/tpm:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
"@org_uber_go_zap//:go_default_library",
],
diff --git a/cmd/init/main.go b/core/cmd/init/main.go
similarity index 93%
rename from cmd/init/main.go
rename to core/cmd/init/main.go
index 3dece3b..a69de2c 100644
--- a/cmd/init/main.go
+++ b/core/cmd/init/main.go
@@ -18,12 +18,12 @@
import (
"fmt"
+ "git.monogon.dev/source/nexantic.git/core/internal/network"
+ "git.monogon.dev/source/nexantic.git/core/internal/node"
+ "git.monogon.dev/source/nexantic.git/core/pkg/tpm"
"os"
"os/signal"
"runtime/debug"
- "git.monogon.dev/source/smalltown.git/internal/network"
- "git.monogon.dev/source/smalltown.git/internal/node"
- "git.monogon.dev/source/smalltown.git/pkg/tpm"
"go.uber.org/zap"
"golang.org/x/sys/unix"
diff --git a/cmd/mkimage/BUILD.bazel b/core/cmd/mkimage/BUILD.bazel
similarity index 88%
rename from cmd/mkimage/BUILD.bazel
rename to core/cmd/mkimage/BUILD.bazel
index 6ef43f6..35e162d 100644
--- a/cmd/mkimage/BUILD.bazel
+++ b/core/cmd/mkimage/BUILD.bazel
@@ -3,7 +3,7 @@
go_library(
name = "go_default_library",
srcs = ["main.go"],
- importpath = "git.monogon.dev/source/smalltown.git/cmd/mkimage",
+ importpath = "git.monogon.dev/source/nexantic.git/core/cmd/mkimage",
visibility = ["//visibility:private"],
deps = [
"@com_github_diskfs_go_diskfs//:go_default_library",
diff --git a/cmd/mkimage/main.go b/core/cmd/mkimage/main.go
similarity index 100%
rename from cmd/mkimage/main.go
rename to core/cmd/mkimage/main.go
diff --git a/core/internal/api/BUILD.bazel b/core/internal/api/BUILD.bazel
new file mode 100644
index 0000000..6e3cb2b
--- /dev/null
+++ b/core/internal/api/BUILD.bazel
@@ -0,0 +1,20 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "go_default_library",
+ srcs = [
+ "cluster.go",
+ "main.go",
+ "setup.go",
+ ],
+ importpath = "git.monogon.dev/source/nexantic.git/core/internal/api",
+ visibility = ["//core:__subpackages__"],
+ deps = [
+ "//core/api/api:go_default_library",
+ "//core/internal/common:go_default_library",
+ "//core/internal/consensus:go_default_library",
+ "@com_github_casbin_casbin//:go_default_library",
+ "@org_golang_google_grpc//:go_default_library",
+ "@org_uber_go_zap//:go_default_library",
+ ],
+)
diff --git a/core/internal/api/cluster.go b/core/internal/api/cluster.go
new file mode 100644
index 0000000..32a5691
--- /dev/null
+++ b/core/internal/api/cluster.go
@@ -0,0 +1,117 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package api
+
+import (
+ "context"
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ schema "git.monogon.dev/source/nexantic.git/core/generated/api"
+ "git.monogon.dev/source/nexantic.git/core/internal/common"
+
+ "errors"
+
+ "go.uber.org/zap"
+)
+
+var (
+ ErrAttestationFailed = errors.New("attestation_failed")
+)
+
+func (s *Server) AddNode(ctx context.Context, req *schema.AddNodeRequest) (*schema.AddNodeResponse, error) {
+ // Setup API client
+ c, err := common.NewSmalltownAPIClient(fmt.Sprintf("%s:%d", req.Host, req.ApiPort))
+ if err != nil {
+ return nil, err
+ }
+
+ // Check attestation
+ nonce := make([]byte, 20)
+ _, err = rand.Read(nonce)
+ if err != nil {
+ return nil, err
+ }
+ hexNonce := hex.EncodeToString(nonce)
+
+ aRes, err := c.Setup.Attest(ctx, &schema.AttestRequest{
+ Challenge: hexNonce,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ //TODO(hendrik): Verify response
+ if aRes.Response != hexNonce {
+ return nil, ErrAttestationFailed
+ }
+
+ // Provision cluster info locally
+ memberID, err := s.consensusService.AddMember(ctx, req.Name, fmt.Sprintf("http://%s:%d", req.Host, req.ConsensusPort))
+ if err != nil {
+ return nil, err
+ }
+
+ s.Logger.Info("Added new node to consensus cluster; provisioning external node now",
+ zap.String("host", req.Host), zap.Uint32("port", req.ApiPort),
+ zap.Uint32("consensus_port", req.ConsensusPort), zap.String("name", req.Name))
+
+ // Provision cluster info externally
+ _, err = c.Setup.ProvisionCluster(ctx, &schema.ProvisionClusterRequest{
+ InitialCluster: s.consensusService.GetInitialClusterString(),
+ ProvisioningToken: req.Token,
+ ExternalHost: req.Host,
+ NodeName: req.Name,
+ TrustBackend: req.TrustBackend,
+ })
+ if err != nil {
+ // Revert Consensus add member - might fail if consensus cannot be established
+ err2 := s.consensusService.RemoveMember(ctx, memberID)
+ if err2 != nil {
+ return nil, fmt.Errorf("Rollback failed after failed provisioning; err=%v; err_rb=%v", err, err2)
+ }
+ return nil, err
+ }
+ s.Logger.Info("Fully provisioned new node",
+ zap.String("host", req.Host), zap.Uint32("port", req.ApiPort),
+ zap.Uint32("consensus_port", req.ConsensusPort), zap.String("name", req.Name),
+ zap.Uint64("member_id", memberID))
+
+ return &schema.AddNodeResponse{}, nil
+}
+
+func (s *Server) RemoveNode(context.Context, *schema.RemoveNodeRequest) (*schema.RemoveNodeRequest, error) {
+ panic("implement me")
+}
+
+func (s *Server) GetNodes(context.Context, *schema.GetNodesRequest) (*schema.GetNodesResponse, error) {
+ nodes := s.consensusService.GetNodes()
+ resNodes := make([]*schema.Node, len(nodes))
+
+ for i, node := range nodes {
+ resNodes[i] = &schema.Node{
+ Id: node.ID,
+ Name: node.Name,
+ Address: node.Address,
+ Synced: node.Synced,
+ }
+ }
+
+ return &schema.GetNodesResponse{
+ Nodes: resNodes,
+ }, nil
+}
diff --git a/core/internal/api/main.go b/core/internal/api/main.go
new file mode 100644
index 0000000..20c3a3a
--- /dev/null
+++ b/core/internal/api/main.go
@@ -0,0 +1,87 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package api
+
+import (
+ "fmt"
+ schema "git.monogon.dev/source/nexantic.git/core/generated/api"
+ "git.monogon.dev/source/nexantic.git/core/internal/common"
+ "git.monogon.dev/source/nexantic.git/core/internal/consensus"
+ "github.com/casbin/casbin"
+ "go.uber.org/zap"
+ "google.golang.org/grpc"
+ "net"
+)
+
+type (
+ Server struct {
+ *common.BaseService
+
+ ruleEnforcer *casbin.Enforcer
+ setupService common.SetupService
+ grpcServer *grpc.Server
+
+ consensusService *consensus.Service
+
+ config *Config
+ }
+
+ Config struct {
+ Port uint16
+ }
+)
+
+func NewApiServer(config *Config, logger *zap.Logger, setupService common.SetupService, consensusService *consensus.Service) (*Server, error) {
+ s := &Server{
+ config: config,
+ setupService: setupService,
+ consensusService: consensusService,
+ }
+
+ s.BaseService = common.NewBaseService("api", logger, s)
+
+ grpcServer := grpc.NewServer()
+ schema.RegisterClusterManagementServer(grpcServer, s)
+ schema.RegisterSetupServiceServer(grpcServer, s)
+
+ s.grpcServer = grpcServer
+
+ return s, nil
+}
+
+func (s *Server) OnStart() error {
+ listenHost := fmt.Sprintf(":%d", s.config.Port)
+ lis, err := net.Listen("tcp", listenHost)
+ if err != nil {
+ s.Logger.Fatal("failed to listen", zap.Error(err))
+ }
+
+ go func() {
+ err = s.grpcServer.Serve(lis)
+ s.Logger.Error("API server failed", zap.Error(err))
+ }()
+
+ s.Logger.Info("GRPC listening", zap.String("host", listenHost))
+
+ return nil
+}
+
+func (s *Server) OnStop() error {
+ s.grpcServer.Stop()
+
+ return nil
+}
diff --git a/core/internal/api/setup.go b/core/internal/api/setup.go
new file mode 100644
index 0000000..943f203
--- /dev/null
+++ b/core/internal/api/setup.go
@@ -0,0 +1,103 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package api
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ schema "git.monogon.dev/source/nexantic.git/core/generated/api"
+)
+
+const (
+ MinNameLength = 3
+)
+
+var (
+ ErrInvalidProvisioningToken = errors.New("invalid provisioning token")
+ ErrInvalidNameLength = fmt.Errorf("name must be at least %d characters long", MinNameLength)
+)
+
+func (s *Server) Setup(c context.Context, r *schema.SetupRequest) (*schema.SetupResponse, error) {
+
+ switch r.Request.(type) {
+ case *schema.SetupRequest_JoinCluster:
+ token, err := s.enterJoinCluster(r.GetJoinCluster())
+ if err != nil {
+ return nil, err
+ }
+
+ return &schema.SetupResponse{
+ Response: &schema.SetupResponse_JoinCluster{
+ JoinCluster: &schema.JoinClusterResponse{
+ ProvisioningToken: token,
+ },
+ },
+ }, nil
+
+ case *schema.SetupRequest_NewCluster:
+ return &schema.SetupResponse{
+ Response: &schema.SetupResponse_NewCluster{
+ NewCluster: &schema.NewClusterResponse{},
+ },
+ }, s.setupNewCluster(r.GetNewCluster())
+ }
+
+ return &schema.SetupResponse{}, nil
+}
+
+func (s *Server) enterJoinCluster(r *schema.JoinClusterRequest) (string, error) {
+ err := s.setupService.EnterJoinClusterMode()
+ if err != nil {
+ return "", err
+ }
+
+ return s.setupService.GetJoinClusterToken(), nil
+}
+
+func (s *Server) setupNewCluster(r *schema.NewClusterRequest) error {
+ if len(r.NodeName) < MinNameLength {
+ return ErrInvalidNameLength
+ }
+ return s.setupService.SetupNewCluster(r.NodeName, r.ExternalHost)
+}
+
+func (s *Server) ProvisionCluster(ctx context.Context, req *schema.ProvisionClusterRequest) (*schema.ProvisionClusterResponse, error) {
+ if len(req.NodeName) < MinNameLength {
+ return nil, ErrInvalidNameLength
+ }
+
+ // Verify provisioning token
+ if s.setupService.GetJoinClusterToken() != req.ProvisioningToken {
+ return nil, ErrInvalidProvisioningToken
+ }
+
+ // Join cluster
+ err := s.setupService.JoinCluster(req.NodeName, req.InitialCluster, req.ExternalHost)
+ if err != nil {
+ return nil, err
+ }
+
+ return &schema.ProvisionClusterResponse{}, nil
+}
+
+func (s *Server) Attest(c context.Context, r *schema.AttestRequest) (*schema.AttestResponse, error) {
+ // TODO implement
+ return &schema.AttestResponse{
+ Response: r.Challenge,
+ }, nil
+}
diff --git a/core/internal/audit/BUILD.bazel b/core/internal/audit/BUILD.bazel
new file mode 100644
index 0000000..9684d55
--- /dev/null
+++ b/core/internal/audit/BUILD.bazel
@@ -0,0 +1,9 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "go_default_library",
+ srcs = ["main.go"],
+ importpath = "git.monogon.dev/source/nexantic.git/core/internal/audit",
+ visibility = ["//:__subpackages__"],
+ deps = ["@io_etcd_go_etcd//clientv3:go_default_library"],
+)
diff --git a/core/internal/audit/main.go b/core/internal/audit/main.go
new file mode 100644
index 0000000..2d43dd0
--- /dev/null
+++ b/core/internal/audit/main.go
@@ -0,0 +1,35 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package audit
+
+import "go.etcd.io/etcd/clientv3"
+
+type (
+ Logger struct {
+ kv *clientv3.KV
+ }
+)
+
+func NewAuditLogger(kv *clientv3.KV) (*Logger, error) {
+ return &Logger{
+ kv: kv,
+ }, nil
+}
+
+func Log(user, action, params string) error {
+ return nil
+}
diff --git a/core/internal/common/BUILD.bazel b/core/internal/common/BUILD.bazel
new file mode 100644
index 0000000..4312b88
--- /dev/null
+++ b/core/internal/common/BUILD.bazel
@@ -0,0 +1,20 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "go_default_library",
+ srcs = [
+ "grpc.go",
+ "service.go",
+ "setup.go",
+ "storage.go",
+ "util.go",
+ ],
+ importpath = "git.monogon.dev/source/nexantic.git/core/internal/common",
+ visibility = ["//core:__subpackages__"],
+ deps = [
+ "//core/api/api:go_default_library",
+ "//core/api/common:go_default_library",
+ "@org_golang_google_grpc//:go_default_library",
+ "@org_uber_go_zap//:go_default_library",
+ ],
+)
diff --git a/core/internal/common/grpc.go b/core/internal/common/grpc.go
new file mode 100644
index 0000000..5512a5c
--- /dev/null
+++ b/core/internal/common/grpc.go
@@ -0,0 +1,52 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package common
+
+import (
+ "git.monogon.dev/source/nexantic.git/core/generated/api"
+
+ "google.golang.org/grpc"
+)
+
+type (
+ SmalltownClient struct {
+ conn *grpc.ClientConn
+
+ Cluster api.ClusterManagementClient
+ Setup api.SetupServiceClient
+ }
+)
+
+func NewSmalltownAPIClient(address string) (*SmalltownClient, error) {
+ s := &SmalltownClient{}
+
+ conn, err := grpc.Dial(address, grpc.WithInsecure())
+ if err != nil {
+ return nil, err
+ }
+ s.conn = conn
+
+ // Setup all client connections
+ s.Cluster = api.NewClusterManagementClient(conn)
+ s.Setup = api.NewSetupServiceClient(conn)
+
+ return s, nil
+}
+
+func (s *SmalltownClient) Close() error {
+ return s.conn.Close()
+}
diff --git a/core/internal/common/service.go b/core/internal/common/service.go
new file mode 100644
index 0000000..3bdc1f9
--- /dev/null
+++ b/core/internal/common/service.go
@@ -0,0 +1,104 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package common
+
+import (
+ "errors"
+ "go.uber.org/zap"
+ "sync"
+)
+
+var (
+ ErrAlreadyRunning = errors.New("service is already running")
+ ErrNotRunning = errors.New("service is not running")
+)
+
+type (
+ // Service represents a subsystem of an application that can be used with a BaseService.
+ Service interface {
+ OnStart() error
+ OnStop() error
+ }
+
+ // BaseService implements utility functionality around a service.
+ BaseService struct {
+ impl Service
+ name string
+
+ Logger *zap.Logger
+
+ mutex sync.Mutex
+ running bool
+ }
+)
+
+func NewBaseService(name string, logger *zap.Logger, impl Service) *BaseService {
+ return &BaseService{
+ Logger: logger,
+ name: name,
+ impl: impl,
+ }
+}
+
+// Start starts the service. This is an atomic operation and should not be called on an already running service.
+func (b *BaseService) Start() error {
+ b.mutex.Lock()
+ defer b.mutex.Unlock()
+
+ if b.running {
+ return ErrAlreadyRunning
+ }
+
+ err := b.impl.OnStart()
+ if err != nil {
+ b.Logger.Error("Failed to start service", zap.String("service", b.name), zap.Error(err))
+ return err
+ }
+
+ b.running = true
+ b.Logger.Info("Started service", zap.String("service", b.name))
+ return nil
+}
+
+// Stop stops the service. THis is an atomic operation and should only be called on a running service.
+func (b *BaseService) Stop() error {
+ b.mutex.Lock()
+ defer b.mutex.Unlock()
+
+ if !b.running {
+ return ErrNotRunning
+ }
+
+ err := b.impl.OnStart()
+ if err != nil {
+ b.Logger.Error("Failed to stop service", zap.String("service", b.name), zap.Error(err))
+
+ return err
+ }
+
+ b.running = false
+ b.Logger.Info("Stopped service", zap.String("service", b.name))
+ return nil
+}
+
+// IsRunning returns whether the service is currently running.
+func (b *BaseService) IsRunning() bool {
+ b.mutex.Lock()
+ defer b.mutex.Unlock()
+
+ return b.running
+}
diff --git a/core/internal/common/setup.go b/core/internal/common/setup.go
new file mode 100644
index 0000000..fd70d0a
--- /dev/null
+++ b/core/internal/common/setup.go
@@ -0,0 +1,35 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package common
+
+type (
+ SetupService interface {
+ CurrentState() SmalltownState
+ GetJoinClusterToken() string
+ SetupNewCluster(name string, externalHost string) error
+ EnterJoinClusterMode() error
+ JoinCluster(name string, clusterString string, externalHost string) error
+ }
+
+ SmalltownState string
+)
+
+const (
+ StateSetupMode SmalltownState = "setup"
+ StateClusterJoinMode SmalltownState = "join"
+ StateConfigured SmalltownState = "configured"
+)
diff --git a/core/internal/common/storage.go b/core/internal/common/storage.go
new file mode 100644
index 0000000..caaa155
--- /dev/null
+++ b/core/internal/common/storage.go
@@ -0,0 +1,37 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package common
+
+import "errors"
+
+type DataPlace uint32
+
+const (
+ PlaceESP DataPlace = 0
+ PlaceData = 1
+)
+
+var (
+ // ErrNotInitialized will be returned when trying to access a place that's not yet initialized
+ ErrNotInitialized = errors.New("This place is not initialized")
+ // ErrUnknownPlace will be returned when trying to access a place that's not known
+ ErrUnknownPlace = errors.New("This place is not known")
+)
+
+type StorageManager interface {
+ GetPathInPlace(place DataPlace, path string) (string, error)
+}
diff --git a/core/internal/common/util.go b/core/internal/common/util.go
new file mode 100644
index 0000000..fc8a72b
--- /dev/null
+++ b/core/internal/common/util.go
@@ -0,0 +1,33 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package common
+
+import "git.monogon.dev/source/nexantic.git/core/generated/common"
+
+func MapToKVs(input map[string]string) []*common.KV {
+ kvs := make([]*common.KV, len(input))
+
+ i := 0
+ for key, item := range input {
+ kvs[i] = &common.KV{
+ Key: key,
+ Value: []byte(item),
+ }
+ }
+
+ return kvs
+}
diff --git a/core/internal/consensus/BUILD.bazel b/core/internal/consensus/BUILD.bazel
new file mode 100644
index 0000000..72d73b4
--- /dev/null
+++ b/core/internal/consensus/BUILD.bazel
@@ -0,0 +1,19 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "go_default_library",
+ srcs = ["consensus.go"],
+ importpath = "git.monogon.dev/source/nexantic.git/core/internal/consensus",
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "//core/internal/common:go_default_library",
+ "@com_github_pkg_errors//:go_default_library",
+ "@io_etcd_go_etcd//clientv3:go_default_library",
+ "@io_etcd_go_etcd//clientv3/namespace:go_default_library",
+ "@io_etcd_go_etcd//embed:go_default_library",
+ "@io_etcd_go_etcd//etcdserver/api/membership:go_default_library",
+ "@io_etcd_go_etcd//pkg/types:go_default_library",
+ "@io_etcd_go_etcd//proxy/grpcproxy/adapter:go_default_library",
+ "@org_uber_go_zap//:go_default_library",
+ ],
+)
diff --git a/core/internal/consensus/consensus.go b/core/internal/consensus/consensus.go
new file mode 100644
index 0000000..e1f59d6
--- /dev/null
+++ b/core/internal/consensus/consensus.go
@@ -0,0 +1,230 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package consensus
+
+import (
+ "context"
+ "fmt"
+ "git.monogon.dev/source/nexantic.git/core/internal/common"
+ "github.com/pkg/errors"
+ "go.etcd.io/etcd/clientv3"
+ "go.etcd.io/etcd/clientv3/namespace"
+ "go.etcd.io/etcd/embed"
+ "go.etcd.io/etcd/etcdserver/api/membership"
+ "go.etcd.io/etcd/pkg/types"
+ "go.etcd.io/etcd/proxy/grpcproxy/adapter"
+ "go.uber.org/zap"
+ "net/url"
+ "os"
+ "strings"
+)
+
+const (
+ DefaultClusterToken = "SIGNOS"
+ DefaultLogger = "zap"
+)
+
+type (
+ Service struct {
+ *common.BaseService
+
+ etcd *embed.Etcd
+ kv clientv3.KV
+ ready bool
+
+ config *Config
+ }
+
+ Config struct {
+ Name string
+ DataDir string
+ InitialCluster string
+ NewCluster bool
+
+ ExternalHost string
+ ListenHost string
+ ListenPort uint16
+ }
+
+ Member struct {
+ ID uint64
+ Name string
+ Address string
+ Synced bool
+ }
+)
+
+func NewConsensusService(config Config, logger *zap.Logger) (*Service, error) {
+ consensusServer := &Service{
+ config: &config,
+ }
+ consensusServer.BaseService = common.NewBaseService("consensus", logger, consensusServer)
+
+ return consensusServer, nil
+}
+
+func (s *Service) OnStart() error {
+ if s.config == nil {
+ return errors.New("config for consensus is nil")
+ }
+
+ cfg := embed.NewConfig()
+
+ // Reset LCUrls because we don't want to expose any client
+ cfg.LCUrls = nil
+
+ apURL, err := url.Parse(fmt.Sprintf("http://%s:%d", s.config.ExternalHost, s.config.ListenPort))
+ if err != nil {
+ return errors.Wrap(err, "invalid external_host or listen_port")
+ }
+
+ lpURL, err := url.Parse(fmt.Sprintf("http://%s:%d", s.config.ListenHost, s.config.ListenPort))
+ if err != nil {
+ return errors.Wrap(err, "invalid listen_host or listen_port")
+ }
+ cfg.APUrls = []url.URL{*apURL}
+ cfg.LPUrls = []url.URL{*lpURL}
+ cfg.ACUrls = []url.URL{}
+
+ cfg.Dir = s.config.DataDir
+ cfg.InitialClusterToken = DefaultClusterToken
+ cfg.Name = s.config.Name
+
+ // Only relevant if creating or joining a cluster; otherwise settings will be ignored
+ if s.config.NewCluster {
+ cfg.ClusterState = "new"
+ cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
+ } else if s.config.InitialCluster != "" {
+ cfg.ClusterState = "existing"
+ cfg.InitialCluster = s.config.InitialCluster
+ }
+
+ cfg.Logger = DefaultLogger
+
+ server, err := embed.StartEtcd(cfg)
+ if err != nil {
+ return err
+ }
+ s.etcd = server
+
+ // Override the logger
+ //*server.GetLogger() = *s.Logger.With(zap.String("component", "etcd"))
+
+ go func() {
+ s.Logger.Info("waiting for etcd to become ready")
+ <-s.etcd.Server.ReadyNotify()
+ s.ready = true
+ s.Logger.Info("etcd is now ready")
+ }()
+
+ // Inject kv client
+ s.kv = clientv3.NewKVFromKVClient(adapter.KvServerToKvClient(s.etcd.Server), nil)
+
+ return nil
+}
+
+func (s *Service) OnStop() error {
+ s.etcd.Close()
+
+ return nil
+}
+
+// IsProvisioned returns whether the node has been setup before and etcd has a data directory
+func (s *Service) IsProvisioned() bool {
+ _, err := os.Stat(s.config.DataDir)
+
+ return !os.IsNotExist(err)
+}
+
+// IsReady returns whether etcd is ready and synced
+func (s *Service) IsReady() bool {
+ return s.ready
+}
+
+// AddMember adds a new etcd member to the cluster
+func (s *Service) AddMember(ctx context.Context, name string, url string) (uint64, error) {
+ urls, err := types.NewURLs([]string{url})
+ if err != nil {
+ return 0, err
+ }
+
+ member := membership.NewMember(name, urls, DefaultClusterToken, nil)
+
+ _, err = s.etcd.Server.AddMember(ctx, *member)
+ if err != nil {
+ return 0, err
+ }
+
+ return uint64(member.ID), nil
+}
+
+// RemoveMember removes a member from the etcd cluster
+func (s *Service) RemoveMember(ctx context.Context, id uint64) error {
+ _, err := s.etcd.Server.RemoveMember(ctx, id)
+ return err
+}
+
+// Health returns the current cluster health
+func (s *Service) Health() {
+}
+
+// GetConfig returns the current consensus config
+func (s *Service) GetConfig() Config {
+ return *s.config
+}
+
+// SetConfig sets the consensus config. Changes are only applied when the service is restarted.
+func (s *Service) SetConfig(config Config) {
+ s.config = &config
+}
+
+// GetInitialClusterString returns the InitialCluster string that can be used to bootstrap a consensus node
+func (s *Service) GetInitialClusterString() string {
+ members := s.etcd.Server.Cluster().Members()
+ clusterString := strings.Builder{}
+
+ for i, m := range members {
+ if i != 0 {
+ clusterString.WriteString(",")
+ }
+ clusterString.WriteString(m.Name)
+ clusterString.WriteString("=")
+ clusterString.WriteString(m.PickPeerURL())
+ }
+
+ return clusterString.String()
+}
+
+// GetNodes returns a list of consensus nodes
+func (s *Service) GetNodes() []Member {
+ members := s.etcd.Server.Cluster().Members()
+ cMembers := make([]Member, len(members))
+ for i, m := range members {
+ cMembers[i] = Member{
+ ID: uint64(m.ID),
+ Name: m.Name,
+ Address: m.PickPeerURL(),
+ Synced: !m.IsLearner,
+ }
+ }
+
+ return cMembers
+}
+
+func (s *Service) GetStore(module, space string) clientv3.KV {
+ return namespace.NewKV(s.kv, fmt.Sprintf("%s:%s", module, space))
+}
diff --git a/core/internal/iam/BUILD.bazel b/core/internal/iam/BUILD.bazel
new file mode 100644
index 0000000..f737d5c
--- /dev/null
+++ b/core/internal/iam/BUILD.bazel
@@ -0,0 +1,22 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+ name = "go_default_library",
+ srcs = [
+ "capabilities.go",
+ "policies.go",
+ ],
+ importpath = "git.monogon.dev/source/nexantic.git/core/internal/iam",
+ visibility = ["//visibility:private"],
+ deps = [
+ "@com_github_open_policy_agent_opa//ast:go_default_library",
+ "@com_github_open_policy_agent_opa//rego:go_default_library",
+ "@com_github_open_policy_agent_opa//util:go_default_library",
+ ],
+)
+
+go_binary(
+ name = "iam",
+ embed = [":go_default_library"],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/core/internal/iam/README.md b/core/internal/iam/README.md
new file mode 100644
index 0000000..07635ad
--- /dev/null
+++ b/core/internal/iam/README.md
@@ -0,0 +1,102 @@
+## Smalltown IAM
+
+There are 4 kinds of elements in Smalltown's Authorization system
+* Identities
+ * User
+ * Key
+ * Module
+* Objects
+ * Key
+ * Secret
+ * Module
+* Policies
+* Permissions
+
+### Identity
+Identities represent an actor that can execute **actions** like editing or interacting with an object.
+
+Identities possess **permissions** and **properties** which can be accessed by policies.
+
+### Objects
+Objects are things that can be interacted with like keys, secrets or modules.
+
+Each object has a **policy** that handles authorization of **actions** performed on it.
+
+When an object is created a default policy is attached which forwards all decisions to the global policy.
+For the first iteration of the system this policy will not be modifiable.
+
+**WARNING**: by modifying a policy, an object could become inaccessible!
+
+### Permissions
+
+Permissions can be assigned to an identity.
+
+| Property | Description | Example |
+|----------|-------------|---------|
+| Allowed Action | Regex specifying the allowed actions | key:meta:edit |
+| Object | Regex specifying the objects this affects | keys:* |
+| Multisig | Number of approvals required | 2 |
+
+Optionally a permission can have a multisig flag that requires N approvals from identities with the same permission.
+
+### Policies
+
+Policies guard actions that are performed on an object.
+
+By default a global policy governs all objects and global actions using an AWS IAM like model.
+
+Potentially a dynamic model using attachable policies could be implemented in the future to allow
+for highly custom models.
+
+A potential graphical representation of a future policy:
+
+
+
+### Global Default Ruleset
+
+This default global policy defines an AWS IAM like permission system.
+
+The following actions are implemented on objects:
+
+| Category | Action | Description | Note |
+|----------|-------------|---------|---------|
+| Object | object:view | Allow to view the object | Cannot be scripted using the policy builder |
+| Object | object:delete | Allow to delete the object |
+| Object | object:attach:normal | Allow to attach the object to a module slot |
+| Object | object:attach:exclusive | Allow to attach the object to an exclusive module slot |
+| Object | object:policy:view | Allow to view the object's attached policy |
+| Object | object:policy:edit | Allow to edit the object's attached policy |
+| Object | object:audit:view | Allow to view the object's audit log |
+| Object:Key | key:sign:eddsa | Allow to sign using the key |
+| Object:Key | key:sign:ecdsa | Allow to sign using the key |
+| Object:Key | key:sign:rsa | Allow to sign using the key |
+| Object:Key | key:encrypt:rsa | Allow to encrypt using the key |
+| Object:Key | key:encrypt:des | Allow to encrypt using the key |
+| Object:Key | key:encrypt:3des| Allow to encrypt using the key |
+| Object:Key | key:encrypt:aes | Allow to encrypt using the key |
+| Object:Key | key:decrypt:rsa | Allow to decrypt using the key |
+| Object:Key | key:decrypt:des | Allow to decrypt using the key |
+| Object:Key | key:decrypt:3des| Allow to decrypt using the key |
+| Object:Key | key:decrypt:aes | Allow to decrypt using the key |
+| Object:Key | key:auth:hmac | Allow to auth messages using the key |
+| Object:Secret | secret:reveal | Allow to reveal a secret to the identity |
+| Object:Module | module:update | Allow to update a module's bytecode | Updates verify the module signature
+| Object:Module | module:config | Allow to configure a module | Assigning objects to slots requires additional permissions on that object
+| Object:Module | module:call:* | Allow to call a function of the module | Function names are defined in the module and vary between modules
+
+The following actions are implemented globally:
+
+| Category | Action | Description | Note |
+|----------|-------------|---------|---------|
+| Object | g:key:generate | Allow to generate a key |
+| Object | g:key:import | Allow to import a key |
+| Object | g:secret:import | Allow to import a secret |
+| Object | g:module:install | Allow to install a module |
+| Object | g:user:create | Allow to create a user |
+| Object | g:user:permission_remove | Allow to create a user | **Privilege Escalation Risk**: Recommend Multisig
+| Object | g:user:permission_add | Allow to create a user | **Privilege Escalation Risk**: Recommend Multisig
+| Object | g:cluster:view | Allow to view cluster nodes
+| Object | g:cluster:add | Allow to add a node to the cluster | **Dangerous**: Recommend Multisig
+| Object | g:cluster:remove | Allow to remove a node from the cluster | **Dangerous**: Recommend Multisig
+| Object | g:config:edit | Allow to edit the global config
+
diff --git a/core/internal/iam/capabilities.go b/core/internal/iam/capabilities.go
new file mode 100644
index 0000000..1c692a3
--- /dev/null
+++ b/core/internal/iam/capabilities.go
@@ -0,0 +1,23 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+type (
+ Capability struct {
+ Name string
+ }
+)
diff --git a/core/internal/iam/policies.go b/core/internal/iam/policies.go
new file mode 100644
index 0000000..b17b623
--- /dev/null
+++ b/core/internal/iam/policies.go
@@ -0,0 +1,69 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "github.com/open-policy-agent/opa/ast"
+ "github.com/open-policy-agent/opa/rego"
+ "github.com/open-policy-agent/opa/util"
+)
+
+type dataSetProfile struct {
+ numTokens int
+ numPaths int
+}
+
+func main() {
+ ctx := context.Background()
+ compiler := ast.NewCompiler()
+ module := ast.MustParseModule(policy)
+
+ compiler.Compile(map[string]*ast.Module{"": module})
+ if compiler.Failed() {
+ }
+
+ r := rego.New(
+ rego.Compiler(compiler),
+ rego.Input(util.MustUnmarshalJSON([]byte(`{
+ "token_id": "deadbeef",
+ "path": "mna",
+ "method": "GET"
+ }`))),
+ rego.Query("data.restauthz"),
+ )
+
+ rs, err := r.Eval(ctx)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Printf("%v", rs)
+}
+
+const policy = `package restauthz
+
+default allow = false
+
+allow {
+ input.method == "GET"
+}
+
+allow {
+ not input.method == "GET"
+}
+`
diff --git a/core/internal/network/BUILD.bazel b/core/internal/network/BUILD.bazel
new file mode 100644
index 0000000..db6467a
--- /dev/null
+++ b/core/internal/network/BUILD.bazel
@@ -0,0 +1,15 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "go_default_library",
+ srcs = ["main.go"],
+ importpath = "git.monogon.dev/source/nexantic.git/core/internal/network",
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "//core/internal/common:go_default_library",
+ "@com_github_insomniacslk_dhcp//dhcpv4/nclient4:go_default_library",
+ "@com_github_vishvananda_netlink//:go_default_library",
+ "@org_golang_x_sys//unix:go_default_library",
+ "@org_uber_go_zap//:go_default_library",
+ ],
+)
diff --git a/core/internal/network/main.go b/core/internal/network/main.go
new file mode 100644
index 0000000..ecb0d18
--- /dev/null
+++ b/core/internal/network/main.go
@@ -0,0 +1,154 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package network
+
+import (
+ "context"
+ "fmt"
+ "git.monogon.dev/source/nexantic.git/core/internal/common"
+ "net"
+ "os"
+
+ "github.com/insomniacslk/dhcp/dhcpv4/nclient4"
+ "github.com/vishvananda/netlink"
+ "go.uber.org/zap"
+ "golang.org/x/sys/unix"
+)
+
+const (
+ resolvConfPath = "/etc/resolv.conf"
+ resolvConfSwapPath = "/etc/resolv.conf.new"
+)
+
+type Service struct {
+ *common.BaseService
+ config Config
+ dhcp4Client *nclient4.Client
+}
+
+type Config struct {
+}
+
+func NewNetworkService(config Config, logger *zap.Logger) (*Service, error) {
+ s := &Service{
+ config: config,
+ }
+ s.BaseService = common.NewBaseService("network", logger, s)
+ return s, nil
+}
+
+func setResolvconf(nameservers []net.IP, searchDomains []string) error {
+ os.Mkdir("/etc", 0755) // Error intentionally not checked
+ newResolvConf, err := os.Create(resolvConfSwapPath)
+ if err != nil {
+ return err
+ }
+ defer newResolvConf.Close()
+ defer os.Remove(resolvConfSwapPath)
+ for _, ns := range nameservers {
+ if _, err := newResolvConf.WriteString(fmt.Sprintf("nameserver %v\n", ns)); err != nil {
+ return err
+ }
+ }
+ for _, searchDomain := range searchDomains {
+ if _, err := newResolvConf.WriteString(fmt.Sprintf("search %v", searchDomain)); err != nil {
+ return err
+ }
+ }
+ newResolvConf.Close()
+ // Atomically swap in new config
+ return unix.Rename(resolvConfSwapPath, resolvConfPath)
+}
+
+func addNetworkRoutes(link netlink.Link, addr net.IPNet, gw net.IP) error {
+ if err := netlink.AddrReplace(link, &netlink.Addr{IPNet: &addr}); err != nil {
+ return err
+ }
+ if err := netlink.RouteAdd(&netlink.Route{
+ Dst: &net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(0, 0, 0, 0)},
+ Gw: gw,
+
+ Scope: netlink.SCOPE_UNIVERSE,
+ }); err != nil {
+ return fmt.Errorf("Failed to add default route: %w", err)
+ }
+ return nil
+}
+
+const (
+ stateInitialize = 1
+ stateSelect = 2
+ stateBound = 3
+ stateRenew = 4
+ stateRebind = 5
+)
+
+var dhcpBroadcastAddr = &net.UDPAddr{IP: net.IP{255, 255, 255, 255}, Port: 67}
+
+// TODO(lorenz): This is a super terrible DHCP client, but it works for QEMU slirp
+func (s *Service) dhcpClient(iface netlink.Link) error {
+ client, err := nclient4.New(iface.Attrs().Name)
+ if err != nil {
+ panic(err)
+ }
+ _, ack, err := client.Request(context.Background())
+ if err != nil {
+ panic(err)
+ }
+ s.Logger.Info("Network service got IP", zap.String("ip", ack.YourIPAddr.String()))
+ if err := setResolvconf(ack.DNS(), []string{}); err != nil {
+ s.Logger.Warn("Failed to set resolvconf", zap.Error(err))
+ }
+ if err := addNetworkRoutes(iface, net.IPNet{IP: ack.YourIPAddr, Mask: ack.SubnetMask()}, ack.GatewayIPAddr); err != nil {
+ s.Logger.Warn("Failed to add routes", zap.Error(err))
+ }
+ return nil
+}
+
+func (s *Service) OnStart() error {
+ s.Logger.Info("Starting network service")
+ links, err := netlink.LinkList()
+ if err != nil {
+ s.Logger.Fatal("Failed to list network links", zap.Error(err))
+ }
+ var ethernetLinks []netlink.Link
+ for _, link := range links {
+ attrs := link.Attrs()
+ if link.Type() == "device" && len(attrs.HardwareAddr) > 0 {
+ if len(attrs.HardwareAddr) == 6 { // Ethernet
+ if attrs.Flags&net.FlagUp != net.FlagUp {
+ netlink.LinkSetUp(link) // Attempt to take up all ethernet links
+ }
+ ethernetLinks = append(ethernetLinks, link)
+ } else {
+ s.Logger.Info("Ignoring non-Ethernet interface", zap.String("interface", attrs.Name))
+ }
+ }
+ }
+ if len(ethernetLinks) == 1 {
+ link := ethernetLinks[0]
+ go s.dhcpClient(link)
+
+ } else {
+ s.Logger.Warn("Network service cannot yet handle more than one interface :(")
+ }
+ return nil
+}
+
+func (s *Service) OnStop() error {
+ return nil
+}
diff --git a/core/internal/node/BUILD.bazel b/core/internal/node/BUILD.bazel
new file mode 100644
index 0000000..0596269
--- /dev/null
+++ b/core/internal/node/BUILD.bazel
@@ -0,0 +1,20 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "go_default_library",
+ srcs = [
+ "main.go",
+ "setup.go",
+ ],
+ importpath = "git.monogon.dev/source/nexantic.git/core/internal/node",
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "//core/internal/api:go_default_library",
+ "//core/internal/common:go_default_library",
+ "//core/internal/consensus:go_default_library",
+ "//core/internal/storage:go_default_library",
+ "@com_github_casbin_casbin//:go_default_library",
+ "@com_github_google_uuid//:go_default_library",
+ "@org_uber_go_zap//:go_default_library",
+ ],
+)
diff --git a/core/internal/node/main.go b/core/internal/node/main.go
new file mode 100644
index 0000000..7494f7a
--- /dev/null
+++ b/core/internal/node/main.go
@@ -0,0 +1,150 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package node
+
+import (
+ "flag"
+ "git.monogon.dev/source/nexantic.git/core/internal/api"
+ "git.monogon.dev/source/nexantic.git/core/internal/common"
+ "git.monogon.dev/source/nexantic.git/core/internal/consensus"
+ "git.monogon.dev/source/nexantic.git/core/internal/storage"
+
+ "github.com/casbin/casbin"
+ "github.com/google/uuid"
+ "go.uber.org/zap"
+)
+
+type (
+ SmalltownNode struct {
+ Api *api.Server
+ Consensus *consensus.Service
+ Storage *storage.Manager
+
+ logger *zap.Logger
+ ruleEnforcer *casbin.Enforcer
+ state common.SmalltownState
+ joinToken string
+ }
+)
+
+func NewSmalltownNode(logger *zap.Logger, apiPort, consensusPort uint16) (*SmalltownNode, error) {
+ flag.Parse()
+ logger.Info("Creating Smalltown node")
+
+ storageManager, err := storage.Initialize(logger.With(zap.String("component", "storage")))
+ if err != nil {
+ logger.Error("Failed to initialize storage manager", zap.Error(err))
+ return nil, err
+ }
+
+ consensusService, err := consensus.NewConsensusService(consensus.Config{
+ Name: "test",
+ ExternalHost: "0.0.0.0",
+ ListenPort: consensusPort,
+ ListenHost: "0.0.0.0",
+ }, logger.With(zap.String("module", "consensus")))
+ if err != nil {
+ return nil, err
+ }
+
+ s := &SmalltownNode{
+ Consensus: consensusService,
+ logger: logger,
+ Storage: storageManager,
+ }
+
+ apiService, err := api.NewApiServer(&api.Config{
+ Port: apiPort,
+ }, logger.With(zap.String("module", "api")), s, s.Consensus)
+ if err != nil {
+ return nil, err
+ }
+
+ s.Api = apiService
+
+ logger.Info("Created SmalltownNode")
+
+ return s, nil
+}
+
+func (s *SmalltownNode) Start() error {
+ s.logger.Info("Starting Smalltown node")
+
+ if s.Consensus.IsProvisioned() {
+ s.logger.Info("Consensus is provisioned")
+ err := s.startFull()
+ if err != nil {
+ return err
+ }
+ } else {
+ s.logger.Info("Consensus is not provisioned")
+ err := s.startForSetup()
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *SmalltownNode) startForSetup() error {
+ s.logger.Info("Initializing subsystems for setup mode")
+ s.state = common.StateSetupMode
+ s.joinToken = uuid.New().String()
+
+ err := s.Api.Start()
+ if err != nil {
+ s.logger.Error("Failed to start the API service", zap.Error(err))
+ return err
+ }
+
+ return nil
+}
+
+func (s *SmalltownNode) startFull() error {
+ s.logger.Info("Initializing subsystems for production")
+ s.state = common.StateConfigured
+
+ err := s.SetupBackend()
+ if err != nil {
+ return err
+ }
+
+ err = s.Consensus.Start()
+ if err != nil {
+ return err
+ }
+
+ err = s.Api.Start()
+ if err != nil {
+ s.logger.Error("Failed to start the API service", zap.Error(err))
+ return err
+ }
+
+ return nil
+}
+
+func (s *SmalltownNode) Stop() error {
+ s.logger.Info("Stopping Smalltown node")
+ return nil
+}
+
+func (s *SmalltownNode) SetupBackend() error {
+ s.logger.Debug("Creating trust backend")
+
+ return nil
+}
diff --git a/core/internal/node/setup.go b/core/internal/node/setup.go
new file mode 100644
index 0000000..28585dd
--- /dev/null
+++ b/core/internal/node/setup.go
@@ -0,0 +1,123 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package node
+
+import (
+ "git.monogon.dev/source/nexantic.git/core/internal/common"
+
+ "errors"
+ "go.uber.org/zap"
+)
+
+var (
+ ErrConsensusAlreadyProvisioned = errors.New("consensus is already provisioned; make sure the data folder is empty")
+ ErrAlreadySetup = errors.New("node is already set up")
+ ErrNotInJoinMode = errors.New("node is not in the cluster join mode")
+ ErrTrustNotInitialized = errors.New("trust backend not initialized")
+ ErrStorageNotInitialized = errors.New("storage not initialized")
+)
+
+func (s *SmalltownNode) CurrentState() common.SmalltownState {
+ return s.state
+}
+
+func (s *SmalltownNode) GetJoinClusterToken() string {
+ return s.joinToken
+}
+
+func (s *SmalltownNode) SetupNewCluster(name string, externalHost string) error {
+ if s.state == common.StateConfigured {
+ return ErrAlreadySetup
+ }
+ dataPath, err := s.Storage.GetPathInPlace(common.PlaceData, "etcd")
+ if err == common.ErrNotInitialized {
+ return ErrStorageNotInitialized
+ } else if err != nil {
+ return err
+ }
+
+ s.logger.Info("Setting up a new cluster", zap.String("name", name), zap.String("external_host", externalHost))
+
+ s.logger.Info("Provisioning consensus")
+
+ // Make sure etcd is not yet provisioned
+ if s.Consensus.IsProvisioned() {
+ return ErrConsensusAlreadyProvisioned
+ }
+
+ // Spin up etcd
+ config := s.Consensus.GetConfig()
+ config.NewCluster = true
+ config.Name = name
+ config.ExternalHost = externalHost
+ config.DataDir = dataPath
+ s.Consensus.SetConfig(config)
+
+ err = s.Consensus.Start()
+ if err != nil {
+ return err
+ }
+
+ // Change system state
+ s.state = common.StateConfigured
+
+ s.logger.Info("New Cluster set up. Node is now fully operational")
+
+ return nil
+}
+
+func (s *SmalltownNode) EnterJoinClusterMode() error {
+ if s.state == common.StateConfigured {
+ return ErrAlreadySetup
+ }
+ s.state = common.StateClusterJoinMode
+
+ s.logger.Info("Node is now in the cluster join mode")
+
+ return nil
+}
+
+func (s *SmalltownNode) JoinCluster(name string, clusterString string, externalHost string) error {
+ if s.state != common.StateClusterJoinMode {
+ return ErrNotInJoinMode
+ }
+
+ s.logger.Info("Joining cluster", zap.String("cluster", clusterString), zap.String("name", name))
+
+ err := s.SetupBackend()
+ if err != nil {
+ return err
+ }
+
+ config := s.Consensus.GetConfig()
+ config.Name = name
+ config.InitialCluster = clusterString
+ config.ExternalHost = externalHost
+ s.Consensus.SetConfig(config)
+
+ // Start consensus
+ err = s.Consensus.Start()
+ if err != nil {
+ return err
+ }
+
+ s.state = common.StateConfigured
+
+ s.logger.Info("Joined cluster. Node is now syncing.")
+
+ return nil
+}
diff --git a/internal/storage/BUILD.bazel b/core/internal/storage/BUILD.bazel
similarity index 60%
rename from internal/storage/BUILD.bazel
rename to core/internal/storage/BUILD.bazel
index 2fe8f56..08c27d6 100644
--- a/internal/storage/BUILD.bazel
+++ b/core/internal/storage/BUILD.bazel
@@ -8,13 +8,13 @@
"find.go",
"xfs.go",
],
- importpath = "git.monogon.dev/source/smalltown.git/internal/storage",
+ importpath = "git.monogon.dev/source/nexantic.git/core/internal/storage",
visibility = ["//:__subpackages__"],
deps = [
- "//internal/common:go_default_library",
- "//pkg/devicemapper:go_default_library",
- "//pkg/sysfs:go_default_library",
- "//pkg/tpm:go_default_library",
+ "//core/internal/common:go_default_library",
+ "//core/pkg/devicemapper:go_default_library",
+ "//core/pkg/sysfs:go_default_library",
+ "//core/pkg/tpm:go_default_library",
"@com_github_rekby_gpt//:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
"@org_uber_go_zap//:go_default_library",
diff --git a/internal/storage/blockdev.go b/core/internal/storage/blockdev.go
similarity index 98%
rename from internal/storage/blockdev.go
rename to core/internal/storage/blockdev.go
index ad56ecd..8bdad12 100644
--- a/internal/storage/blockdev.go
+++ b/core/internal/storage/blockdev.go
@@ -20,7 +20,7 @@
"encoding/binary"
"encoding/hex"
"fmt"
- "git.monogon.dev/source/smalltown.git/pkg/devicemapper"
+ "git.monogon.dev/source/nexantic.git/core/pkg/devicemapper"
"os"
"syscall"
diff --git a/internal/storage/data.go b/core/internal/storage/data.go
similarity index 97%
rename from internal/storage/data.go
rename to core/internal/storage/data.go
index 618f68f..e6df103 100644
--- a/internal/storage/data.go
+++ b/core/internal/storage/data.go
@@ -18,8 +18,8 @@
import (
"fmt"
- "git.monogon.dev/source/smalltown.git/internal/common"
- "git.monogon.dev/source/smalltown.git/pkg/tpm"
+ "git.monogon.dev/source/nexantic.git/core/internal/common"
+ "git.monogon.dev/source/nexantic.git/core/pkg/tpm"
"io/ioutil"
"os"
"os/exec"
diff --git a/internal/storage/find.go b/core/internal/storage/find.go
similarity index 98%
rename from internal/storage/find.go
rename to core/internal/storage/find.go
index 3a708c2..0ba1ca0 100644
--- a/internal/storage/find.go
+++ b/core/internal/storage/find.go
@@ -18,7 +18,7 @@
import (
"fmt"
- "git.monogon.dev/source/smalltown.git/pkg/sysfs"
+ "git.monogon.dev/source/nexantic.git/core/pkg/sysfs"
"io/ioutil"
"os"
"path/filepath"
diff --git a/internal/storage/xfs.go b/core/internal/storage/xfs.go
similarity index 100%
rename from internal/storage/xfs.go
rename to core/internal/storage/xfs.go
diff --git a/pkg/devicemapper/BUILD.bazel b/core/pkg/devicemapper/BUILD.bazel
similarity index 82%
rename from pkg/devicemapper/BUILD.bazel
rename to core/pkg/devicemapper/BUILD.bazel
index a56718b..2395881 100644
--- a/pkg/devicemapper/BUILD.bazel
+++ b/core/pkg/devicemapper/BUILD.bazel
@@ -3,7 +3,7 @@
go_library(
name = "go_default_library",
srcs = ["devicemapper.go"],
- importpath = "git.monogon.dev/source/smalltown.git/pkg/devicemapper",
+ importpath = "git.monogon.dev/source/nexantic.git/core/pkg/devicemapper",
visibility = ["//visibility:public"],
deps = [
"@com_github_pkg_errors//:go_default_library",
diff --git a/pkg/devicemapper/devicemapper.go b/core/pkg/devicemapper/devicemapper.go
similarity index 100%
rename from pkg/devicemapper/devicemapper.go
rename to core/pkg/devicemapper/devicemapper.go
diff --git a/pkg/sysfs/BUILD.bazel b/core/pkg/sysfs/BUILD.bazel
similarity index 70%
rename from pkg/sysfs/BUILD.bazel
rename to core/pkg/sysfs/BUILD.bazel
index 28c4008..4272089 100644
--- a/pkg/sysfs/BUILD.bazel
+++ b/core/pkg/sysfs/BUILD.bazel
@@ -3,6 +3,6 @@
go_library(
name = "go_default_library",
srcs = ["uevents.go"],
- importpath = "git.monogon.dev/source/smalltown.git/pkg/sysfs",
+ importpath = "git.monogon.dev/source/nexantic.git/core/pkg/sysfs",
visibility = ["//visibility:public"],
)
diff --git a/pkg/sysfs/uevents.go b/core/pkg/sysfs/uevents.go
similarity index 100%
rename from pkg/sysfs/uevents.go
rename to core/pkg/sysfs/uevents.go
diff --git a/pkg/tpm/BUILD.bazel b/core/pkg/tpm/BUILD.bazel
similarity index 83%
rename from pkg/tpm/BUILD.bazel
rename to core/pkg/tpm/BUILD.bazel
index 2325170..6803e8a 100644
--- a/pkg/tpm/BUILD.bazel
+++ b/core/pkg/tpm/BUILD.bazel
@@ -3,10 +3,10 @@
go_library(
name = "go_default_library",
srcs = ["tpm.go"],
- importpath = "git.monogon.dev/source/smalltown.git/pkg/tpm",
+ importpath = "git.monogon.dev/source/nexantic.git/core/pkg/tpm",
visibility = ["//visibility:public"],
deps = [
- "//pkg/sysfs:go_default_library",
+ "//core/pkg/sysfs:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@com_github_google_go_tpm//tpm2:go_default_library",
"@com_github_google_go_tpm_tools//proto:go_default_library",
diff --git a/pkg/tpm/tpm.go b/core/pkg/tpm/tpm.go
similarity index 98%
rename from pkg/tpm/tpm.go
rename to core/pkg/tpm/tpm.go
index 2a59094..4638453 100644
--- a/pkg/tpm/tpm.go
+++ b/core/pkg/tpm/tpm.go
@@ -19,7 +19,7 @@
import (
"crypto/rand"
"fmt"
- "git.monogon.dev/source/smalltown.git/pkg/sysfs"
+ "git.monogon.dev/source/nexantic.git/core/pkg/sysfs"
"io"
"os"
"path/filepath"
diff --git a/scripts/BUILD b/core/scripts/BUILD
similarity index 70%
rename from scripts/BUILD
rename to core/scripts/BUILD
index 2761d41..81a8d6c 100644
--- a/scripts/BUILD
+++ b/core/scripts/BUILD
@@ -1,10 +1,10 @@
sh_binary(
name = "launch",
srcs = ["launch.sh"],
- tags = ["local"],
data = [
- "@//:image",
- "@//:swtpm_data",
+ "@//core:image",
+ "@//core:swtpm_data",
"@edk2//:firmware",
],
+ tags = ["local"],
)
diff --git a/scripts/launch.sh b/core/scripts/launch.sh
similarity index 81%
rename from scripts/launch.sh
rename to core/scripts/launch.sh
index 69cc5fe..4aa2d9c 100755
--- a/scripts/launch.sh
+++ b/core/scripts/launch.sh
@@ -1,12 +1,12 @@
#!/bin/sh
-swtpm socket --tpmstate dir=tpm --ctrl type=unixio,path=tpm-socket --tpm2 &
+swtpm socket --tpmstate dir=core/tpm --ctrl type=unixio,path=tpm-socket --tpm2 &
qemu-system-x86_64 \
-cpu host -smp sockets=1,cpus=1,cores=2,threads=2,maxcpus=4 -m 1024 -machine q35 -enable-kvm -nographic -nodefaults \
-drive if=pflash,format=raw,readonly,file=external/edk2/OVMF_CODE.fd \
-drive if=pflash,format=raw,snapshot=on,file=external/edk2/OVMF_VARS.fd \
- -drive if=virtio,format=raw,snapshot=on,cache=unsafe,file=smalltown.img \
+ -drive if=virtio,format=raw,snapshot=on,cache=unsafe,file=core/smalltown.img \
-netdev user,id=net0,hostfwd=tcp::7833-:7833,hostfwd=tcp::7834-:7834 \
-device virtio-net-pci,netdev=net0 \
-chardev socket,id=chrtpm,path=tpm-socket \
diff --git a/scripts/bazel_copy_generated_for_ide.sh b/scripts/bazel_copy_generated_for_ide.sh
index e6c8ee0..0473e40 100755
--- a/scripts/bazel_copy_generated_for_ide.sh
+++ b/scripts/bazel_copy_generated_for_ide.sh
@@ -2,5 +2,5 @@
# Copy generated Go protobuf libraries to a place where a non-Bazel-aware IDE can find them.
# Locally, a symlink will be sufficient.
-mkdir -p generated
-rsync -av --delete --exclude '*.a' bazel-bin/api/*/linux_amd64_stripped/*/git.monogon.dev/source/smalltown.git/generated/* generated/
+mkdir -p smalltown/generated
+rsync -av --delete --exclude '*.a' bazel-bin/smalltown/api/*/linux_amd64_stripped/*/git.monogon.dev/source/nexantic.git/smalltown/generated/* smalltown/generated/
diff --git a/scripts/create_container.sh b/scripts/create_container.sh
index 5b4c65d..0067d72 100755
--- a/scripts/create_container.sh
+++ b/scripts/create_container.sh
@@ -16,7 +16,7 @@
fi
# Rebuild base image
-podman build -t smalltown-builder .
+podman build -t nexantic-builder build
# Set up SELinux contexts to prevent the container from writing to
# files that would allow for easy breakouts via tools ran on the host.
@@ -35,5 +35,5 @@
--device /dev/kvm \
--privileged \
--userns=keep-id \
- --name=smalltown-dev \
- smalltown-builder
+ --name=nexantic-dev \
+ nexantic-builder
diff --git a/scripts/destroy_container.sh b/scripts/destroy_container.sh
index 13fefe3..11d252d 100755
--- a/scripts/destroy_container.sh
+++ b/scripts/destroy_container.sh
@@ -1,4 +1,4 @@
#!/bin/bash
-podman stop smalltown-dev
-podman rm smalltown-dev --force
+podman stop nexantic-dev
+podman rm nexantic-dev --force
diff --git a/scripts/run_in_container.sh b/scripts/run_in_container.sh
index 8b60bd6..0fd2731 100755
--- a/scripts/run_in_container.sh
+++ b/scripts/run_in_container.sh
@@ -1,4 +1,4 @@
#!/bin/bash
set -euo pipefail
-podman exec -it smalltown-dev $@
\ No newline at end of file
+podman exec -it nexantic-dev $@