1
0
mirror of https://github.com/rancher/os.git synced 2025-09-06 01:01:43 +00:00

Add TPM and MachineRegister support

This commit is contained in:
Darren Shepherd
2021-10-29 12:20:35 -07:00
parent db84312450
commit 901973e5f6
40 changed files with 3390 additions and 291 deletions

View File

@@ -1,9 +1,9 @@
FROM opensuse/leap:15.3 AS build
RUN zypper ref
RUN zypper in -y squashfs xorriso go1.16 upx busybox-static curl tar git gzip
RUN curl -Lo /usr/bin/luet https://github.com/mudler/luet/releases/download/0.18.1/luet-0.18.1-linux-$(go env GOARCH) && \
RUN curl -Lo /usr/bin/luet https://github.com/mudler/luet/releases/download/0.20.5/luet-0.20.5-linux-$(go env GOARCH) && \
chmod +x /usr/bin/luet
RUN curl -Lo /usr/bin/rancherd https://github.com/rancher/rancherd/releases/download/v0.0.1-alpha10/rancherd-$(go env GOARCH) && \
RUN curl -Lo /usr/bin/rancherd https://github.com/rancher/rancherd/releases/download/v0.0.1-alpha11/rancherd-$(go env GOARCH) && \
chmod +x /usr/bin/rancherd
RUN curl -L https://get.helm.sh/helm-v3.7.1-linux-$(go env GOARCH).tar.gz | tar xzf - -C /usr/bin --strip-components=1
COPY go.mod go.sum /usr/src/
@@ -90,6 +90,7 @@ RUN zypper in -y \
kernel-firmware-qlogic \
kernel-firmware-realtek \
kernel-firmware-usb-network \
libtspi1 \
less \
lshw \
lsof \

View File

@@ -4,7 +4,7 @@ RUN zypper ref
ARG DAPPER_HOST_ARCH
ENV ARCH $DAPPER_HOST_ARCH
RUN zypper in -y bash git gcc docker vim less file curl wget ca-certificates make mkisofs go1.16 qemu-tools
RUN zypper in -y bash git gcc docker vim less file curl wget ca-certificates make mkisofs go1.16 qemu-tools trousers-devel
RUN go get golang.org/x/tools/cmd/goimports
RUN if [ "${ARCH}" == "amd64" ]; then \
curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.40.1; \

30
Dockerfile.kvm Normal file
View File

@@ -0,0 +1,30 @@
FROM opensuse/leap:15.3
RUN zypper ref
RUN zypper install -y socat net-tools-deprecated libtasn1-devel gnutls-devel libseccomp-devel json-glib-devel system-user-tss git
RUN zypper install -y autoconf
RUN zypper install -y automake
RUN git clone https://github.com/stefanberger/swtpm.git /usr/src/swtpm
RUN zypper install -y libtool
RUN zypper install -y gcc
RUN zypper install -y libopenssl-devel
RUN git clone https://github.com/stefanberger/libtpms.git /usr/src/libtpms
RUN zypper install -y gcc-c++
RUN zypper install -y make
RUN zypper install -y expect
RUN zypper install -y sudo
RUN cd /usr/src/libtpms && \
./autogen.sh --with-openssl --with-tpm2 && \
make -j4 && \
make install
RUN cd /usr/src/swtpm && \
./autogen.sh --prefix=/usr --libdir=/usr/lib64 --with-openssl --with-tss-user=root --with-tss-group=tss && \
make -j4 && \
sudo make -j4 && \
sudo make install
RUN zypper install -y qemu-x86 qemu-arm qemu-tools
COPY scripts/qemu-in-container /usr/bin/
ENTRYPOINT ["/usr/bin/qemu-in-container"]
RUN chmod +s /usr/lib/qemu-bridge-helper
RUN echo 'allow all' > /etc/qemu/bridge.conf

View File

@@ -34,9 +34,14 @@ rules:
- apiGroups:
- management.cattle.io
resources:
- 'settings'
- 'clusterregistrationtokens'
verbs:
- '*'
- apiGroups:
- management.cattle.io
resources:
- 'settings'
verbs:
- 'get'
- 'watch'
- 'list'
@@ -44,8 +49,6 @@ rules:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
#resourceNames:
#- managedosimages.rancheros.cattle.io
verbs:
- '*'
- apiGroups:

View File

@@ -14,6 +14,8 @@ var (
automatic = flag.Bool("automatic", false, "Check for and run automatic installation")
printConfig = flag.Bool("print-config", false, "Print effective configuration and exit")
configFile = flag.String("config-file", "", "Config file to use, local file or http/tftp URL")
powerOff = flag.Bool("power-off", false, "Power off after installation")
yes = flag.Bool("y", false, "Do not prompt for questions")
)
func main() {
@@ -31,7 +33,7 @@ func main() {
return
}
if err := install.Run(*automatic, *configFile); err != nil {
if err := install.Run(*automatic, *configFile, *powerOff, *yes); err != nil {
logrus.Fatal(err)
}
}

View File

@@ -1,3 +1,3 @@
[Unit]
ConditionPathExists=!/run/cos/live_mode
ConditionPathExists=!/run/cos/rescue_mode
ConditionPathExists=!/run/cos/recovery_mode

View File

@@ -18,5 +18,6 @@ LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
StandardOutput=journal+console
TimeoutStartSec=0
ExecStart=/usr/bin/rancherd bootstrap

View File

@@ -15,6 +15,14 @@ spec:
EOF
}
if [ -e /etc/rancher/rke2/rke2.yaml ]; then
export KUBECONFIG=/etc/rancher/rke2/rke2.yaml
elif [ -e /etc/rancher/k3s/k3s.yaml ]; then
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
else
exit 0
fi
PULL_POLICY=IfNotPresent
if [ "$IMAGE_TAG" = dev ]; then
PULL_POLICY=Always
@@ -29,6 +37,11 @@ helm upgrade \
--set image.imagePullPolicy=${PULL_POLICY} \
rancheros-operator /usr/share/rancher/os2/rancheros-operator-chart.tgz
while ! kubectl get crd managedosimages.rancheros.cattle.io; do
echo Waiting for RancherOS Operator to be running
sleep 15
done
while ! manifest | kubectl apply -f -; do
sleep 15
done

39
go.mod
View File

@@ -2,25 +2,58 @@ module github.com/rancher/os2
go 1.16
replace (
k8s.io/api => k8s.io/api v0.22.2
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.22.2
k8s.io/apimachinery => k8s.io/apimachinery v0.22.2
k8s.io/apiserver => k8s.io/apiserver v0.22.2
k8s.io/cli-runtime => k8s.io/cli-runtime v0.22.2
k8s.io/client-go => k8s.io/client-go v0.22.2
k8s.io/cloud-provider => k8s.io/cloud-provider v0.22.2
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.22.2
k8s.io/code-generator => k8s.io/code-generator v0.22.2
k8s.io/component-base => k8s.io/component-base v0.22.2
k8s.io/component-helpers => k8s.io/component-helpers v0.22.2
k8s.io/controller-manager => k8s.io/controller-manager v0.22.2
k8s.io/cri-api => k8s.io/cri-api v0.22.2
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.22.2
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.22.2
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.22.2
k8s.io/kube-proxy => k8s.io/kube-proxy v0.22.2
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.22.2
k8s.io/kubectl => k8s.io/kubectl v0.22.2
k8s.io/kubelet => k8s.io/kubelet v0.22.2
k8s.io/kubernetes => k8s.io/kubernetes v1.22.2
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.22.2
k8s.io/metrics => k8s.io/metrics v0.22.2
k8s.io/mount-utils => k8s.io/mount-utils v0.22.2
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.22.2
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.22.2
)
require (
github.com/google/certificate-transparency-go v1.1.2
github.com/google/go-attestation v0.3.2
github.com/gorilla/websocket v1.4.2
github.com/mattn/go-isatty v0.0.12
github.com/pin/tftp v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1
github.com/rancher/fleet/pkg/apis v0.0.0-20210927195558-4aaa778d23dd
github.com/rancher/lasso v0.0.0-20210709145333-6c6cd7fd6607
github.com/rancher/rancher/pkg/apis v0.0.0-20211013185633-a636bda2a00e
github.com/rancher/rancherd v0.0.1-alpha9.0.20211028172625-bdf5642d62d5
github.com/rancher/steve v0.0.0-20210922195510-7224dc21013d
github.com/rancher/system-upgrade-controller/pkg/apis v0.0.0-20210929162341-5e6e996d9486
github.com/rancher/wrangler v0.8.7
github.com/sirupsen/logrus v1.7.0
github.com/sirupsen/logrus v1.8.1
github.com/tredoe/osutil v1.0.5
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
gopkg.in/pin/tftp.v2 v2.1.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gotest.tools v2.2.0+incompatible
k8s.io/api v0.22.2
k8s.io/apimachinery v0.22.2
k8s.io/client-go v12.0.0+incompatible
sigs.k8s.io/controller-runtime v0.9.0-beta.0
sigs.k8s.io/yaml v1.2.0
)
replace k8s.io/client-go => k8s.io/client-go v0.22.2

849
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -48,6 +48,7 @@ nav:
- installation.md
- upgrade.md
- configuration.md
- customizing.md
- dashboard.md
- operator.md
- versions.md

View File

@@ -1,12 +1,7 @@
if [ -z "$KUBECONFIG" ]; then
if [ -e /etc/rancher/rke2 ]; then
export KUBECONFIG=/etc/rancher/rke2/rke2.yaml
fi
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
fi
if [ -d /var/lib/rancher/rke2/bin ]; then
export PATH="${PATH}:/var/lib/rancher/rke2/bin"
export KUBECONFIG="/etc/rancher/k3s/k3s.yaml:/etc/rancher/rke2/rke2.yaml"
fi
export PATH="${PATH}:/var/lib/rancher/rke2/bin"
if [ -z "$CONTAINER_RUNTIME_ENDPOINT" ]; then
export CONTAINER_RUNTIME_ENDPOINT=unix:///var/run/k3s/containerd/containerd.sock
fi

View File

@@ -1,6 +1,8 @@
package v1
import (
"github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
"github.com/rancher/wrangler/pkg/genericcondition"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -8,6 +10,30 @@ import (
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type MachineRegistration struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MachineRegistrationSpec `json:"spec"`
Status MachineRegistrationStatus `json:"status"`
}
type MachineRegistrationSpec struct {
MachineName string `json:"machineName,omitempty"`
MachineInventoryLabels map[string]string `json:"machineInventoryLabels,omitempty"`
MachineInventoryAnnotations map[string]string `json:"machineInventoryAnnotations,omitempty"`
CloudConfig *v1alpha1.GenericMap `json:"cloudConfig,omitempty"`
}
type MachineRegistrationStatus struct {
Conditions []genericcondition.GenericCondition `json:"conditions,omitempty"`
RegistrationURL string `json:"registrationURL,omitempty"`
RegistrationToken string `json:"registrationToken,omitempty"`
}
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type MachineInventory struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
@@ -17,19 +43,20 @@ type MachineInventory struct {
}
type MachineInventorySpec struct {
ClusterName string `json:"clusterName,omitempty"`
TPMHash string `json:"tpmHash,omitempty"`
SMBIOS *v1alpha1.GenericMap `json:"smbios,omitempty"`
ClusterName string `json:"clusterName"`
MachineTokenSecretName string `json:"machineTokenSecretName,omitempty"`
Config MachineRuntimeConfig `json:"config,omitempty"`
}
type MachineRuntimeConfig struct {
Role string `json:"role,omitempty"`
Role string `json:"role"`
NodeName string `json:"nodeName,omitempty"`
Address string `json:"address,omitempty"`
InternalAddress string `json:"internalAddress,omitempty"`
Taints []corev1.Taint `json:"taints,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
ConfigValues map[string]string `json:"extraConfig,omitempty"`
Labels map[string]string `json:"labels"`
}
type MachineInventoryStatus struct {

View File

@@ -23,6 +23,7 @@ package v1
import (
v1alpha1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
upgradecattleiov1 "github.com/rancher/system-upgrade-controller/pkg/apis/upgrade.cattle.io/v1"
genericcondition "github.com/rancher/wrangler/pkg/genericcondition"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
@@ -92,6 +93,10 @@ func (in *MachineInventoryList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MachineInventorySpec) DeepCopyInto(out *MachineInventorySpec) {
*out = *in
if in.SMBIOS != nil {
in, out := &in.SMBIOS, &out.SMBIOS
*out = (*in).DeepCopy()
}
in.Config.DeepCopyInto(&out.Config)
return
}
@@ -122,6 +127,122 @@ func (in *MachineInventoryStatus) DeepCopy() *MachineInventoryStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MachineRegistration) DeepCopyInto(out *MachineRegistration) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineRegistration.
func (in *MachineRegistration) DeepCopy() *MachineRegistration {
if in == nil {
return nil
}
out := new(MachineRegistration)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *MachineRegistration) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MachineRegistrationList) DeepCopyInto(out *MachineRegistrationList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]MachineRegistration, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineRegistrationList.
func (in *MachineRegistrationList) DeepCopy() *MachineRegistrationList {
if in == nil {
return nil
}
out := new(MachineRegistrationList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *MachineRegistrationList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MachineRegistrationSpec) DeepCopyInto(out *MachineRegistrationSpec) {
*out = *in
if in.MachineInventoryLabels != nil {
in, out := &in.MachineInventoryLabels, &out.MachineInventoryLabels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.MachineInventoryAnnotations != nil {
in, out := &in.MachineInventoryAnnotations, &out.MachineInventoryAnnotations
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.CloudConfig != nil {
in, out := &in.CloudConfig, &out.CloudConfig
*out = (*in).DeepCopy()
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineRegistrationSpec.
func (in *MachineRegistrationSpec) DeepCopy() *MachineRegistrationSpec {
if in == nil {
return nil
}
out := new(MachineRegistrationSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MachineRegistrationStatus) DeepCopyInto(out *MachineRegistrationStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]genericcondition.GenericCondition, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineRegistrationStatus.
func (in *MachineRegistrationStatus) DeepCopy() *MachineRegistrationStatus {
if in == nil {
return nil
}
out := new(MachineRegistrationStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MachineRuntimeConfig) DeepCopyInto(out *MachineRuntimeConfig) {
*out = *in
@@ -139,13 +260,6 @@ func (in *MachineRuntimeConfig) DeepCopyInto(out *MachineRuntimeConfig) {
(*out)[key] = val
}
}
if in.ConfigValues != nil {
in, out := &in.ConfigValues, &out.ConfigValues
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}

View File

@@ -43,6 +43,23 @@ func NewMachineInventory(namespace, name string, obj MachineInventory) *MachineI
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// MachineRegistrationList is a list of MachineRegistration resources
type MachineRegistrationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []MachineRegistration `json:"items"`
}
func NewMachineRegistration(namespace, name string, obj MachineRegistration) *MachineRegistration {
obj.APIVersion, obj.Kind = SchemeGroupVersion.WithKind("MachineRegistration").ToAPIVersionAndKind()
obj.Name = name
obj.Namespace = namespace
return &obj
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// ManagedOSImageList is a list of ManagedOSImage resources
type ManagedOSImageList struct {
metav1.TypeMeta `json:",inline"`

View File

@@ -29,6 +29,7 @@ import (
var (
MachineInventoryResourceName = "machineinventories"
MachineRegistrationResourceName = "machineregistrations"
ManagedOSImageResourceName = "managedosimages"
)
@@ -55,6 +56,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&MachineInventory{},
&MachineInventoryList{},
&MachineRegistration{},
&MachineRegistrationList{},
&ManagedOSImage{},
&ManagedOSImageList{},
)

View File

@@ -18,11 +18,14 @@ type Install struct {
Token string `json:"-"`
Role string `json:"-"`
Password string `json:"password,omitempty"`
RegistrationURL string `json:"registrationUrl,omitempty"`
RegistrationCACert string `json:"registrationCaCert,omitempty"`
}
type Config struct {
SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"`
RancherOS RancherOS `json:"rancheros,omitempty"`
Data map[string]interface{} `json:"-"`
}
type YipConfig struct {

View File

@@ -1,16 +1,22 @@
package config
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"regexp"
"strings"
"time"
"github.com/rancher/os2/pkg/dmidecode"
"github.com/rancher/rancherd/pkg/tpm"
values "github.com/rancher/wrangler/pkg/data"
"github.com/rancher/wrangler/pkg/data/convert"
schemas2 "github.com/rancher/wrangler/pkg/schemas"
"github.com/sirupsen/logrus"
"sigs.k8s.io/yaml"
)
@@ -60,30 +66,33 @@ func readFileFunc(path string) func() (map[string]interface{}, error) {
}
}
func readNested(data map[string]interface{}) (map[string]interface{}, error) {
func readNested(data map[string]interface{}, overlay bool) (map[string]interface{}, error) {
var (
nestedConfigFiles = convert.ToStringSlice(values.GetValueN(data, "rancheros", "install", "configUrl"))
funcs []reader
)
if overlay {
funcs = append(funcs, func() (map[string]interface{}, error) {
return data, nil
})
}
for _, nestedConfigFile := range nestedConfigFiles {
funcs = append(funcs, readFileFunc(nestedConfigFile))
}
if !overlay {
funcs = append(funcs, func() (map[string]interface{}, error) {
return data, nil
})
}
return merge(funcs...)
}
func readFile(path string) (result map[string]interface{}, _ error) {
result = map[string]interface{}{}
defer func() {
if v, ok := result["install"]; ok {
values.PutValue(result, v, "rancheros", "install")
}
}()
switch {
case strings.HasPrefix(path, "http://"):
@@ -117,7 +126,7 @@ func readFile(path string) (result map[string]interface{}, _ error) {
return nil, err
}
return readNested(data)
return readNested(data, false)
}
type reader func() (map[string]interface{}, error)
@@ -145,6 +154,20 @@ func readConfigMap(cfg string) (map[string]interface{}, error) {
if cfg != "" {
values.PutValue(data, cfg, "rancheros", "install", "configUrl")
}
registrationURL := convert.ToString(values.GetValueN(data, "rancheros", "install", "registrationUrl"))
registrationCA := convert.ToString(values.GetValueN(data, "rancheros", "install", "registrationCaCert"))
if registrationURL != "" {
for {
newData, err := returnRegistrationData(registrationURL, registrationCA)
if err == nil {
return newData, nil
}
logrus.Errorf("failed to read registration URL %s, retrying: %v", registrationURL, err)
time.Sleep(15 * time.Second)
}
}
return data, nil
}
@@ -157,12 +180,7 @@ func ToFile(cfg Config, output string) error {
}
func ToBytes(cfg Config) ([]byte, error) {
data, err := merge(readFileFunc(cfg.RancherOS.Install.ConfigURL), func() (map[string]interface{}, error) {
return convert.EncodeToMap(cfg)
})
if err != nil {
return nil, err
}
data := values.MergeMaps(nil, cfg.Data)
values.RemoveValue(data, "install")
values.RemoveValue(data, "rancheros", "install")
bytes, err := yaml.Marshal(data)
@@ -179,7 +197,41 @@ func ReadConfig(cfg string) (result Config, err error) {
return result, err
}
return result, convert.ToObj(data, &result)
if err := convert.ToObj(data, &result); err != nil {
return result, err
}
result.Data = data
return result, nil
}
func returnRegistrationData(url, ca string) (map[string]interface{}, error) {
smbios, err := getSMBiosHeaders()
if err != nil {
return nil, err
}
data, err := tpm.Get([]byte(ca), url, smbios)
if err != nil {
return nil, err
}
logrus.Infof("Retrieved config from registrationURL: %s", data)
result := map[string]interface{}{}
return result, json.Unmarshal(data, &result)
}
func getSMBiosHeaders() (http.Header, error) {
smbios, err := dmidecode.Decode()
if err != nil {
return nil, err
}
smbiosData, err := json.Marshal(smbios)
if err != nil {
return nil, err
}
header := http.Header{}
header.Set("X-Cattle-Smbios", base64.StdEncoding.EncodeToString(smbiosData))
return header, nil
}
func readCmdline() (map[string]interface{}, error) {
@@ -221,5 +273,9 @@ func readCmdline() (map[string]interface{}, error) {
}
}
return readNested(data)
if err := schema.Mapper.ToInternal(data); err != nil {
return nil, err
}
return readNested(data, true)
}

View File

@@ -16,7 +16,7 @@ type FuzzyNames struct {
func (f *FuzzyNames) ToInternal(data data.Object) error {
for k, v := range data {
if newK, ok := f.names[k]; ok && newK != k {
if newK, ok := f.names[strings.ToLower(k)]; ok && newK != k {
data[newK] = v
}
}

View File

@@ -43,7 +43,7 @@ func (h *handler) OnMachineInventoryRemove(key string, machine *v1.MachineInvent
}
func (h *handler) OnMachineInventory(machine *v1.MachineInventory, status v1.MachineInventoryStatus) (v1.MachineInventoryStatus, error) {
if machine == nil {
if machine == nil || machine.Spec.ClusterName == "" {
return status, nil
}
@@ -56,7 +56,7 @@ func (h *handler) OnMachineInventory(machine *v1.MachineInventory, status v1.Mac
return status, fmt.Errorf("waiting for mgmt cluster to be created for prov cluster %s/%s", machine.Namespace, machine.Spec.ClusterName)
}
crtName := name.SafeConcatName(cluster.Status.ClusterName, machine.Name, "-token")
crtName := name.SafeConcatName(cluster.Status.ClusterName, machine.Name, "token")
_, err = h.clusterRegistrationTokenCache.Get(cluster.Status.ClusterName, crtName)
if apierrors.IsNotFound(err) {
_, err = h.clusterRegistrationTokenClient.Create(&v3.ClusterRegistrationToken{

View File

@@ -0,0 +1,52 @@
package registration
import (
"context"
"fmt"
v1 "github.com/rancher/os2/pkg/apis/rancheros.cattle.io/v1"
"github.com/rancher/os2/pkg/clients"
ranchercontrollers "github.com/rancher/os2/pkg/generated/controllers/management.cattle.io/v3"
roscontrollers "github.com/rancher/os2/pkg/generated/controllers/rancheros.cattle.io/v1"
"github.com/rancher/wrangler/pkg/randomtoken"
)
type handler struct {
settingsCache ranchercontrollers.SettingCache
}
func Register(ctx context.Context, clients *clients.Clients) {
h := handler{
settingsCache: clients.Rancher.Setting().Cache(),
}
roscontrollers.RegisterMachineRegistrationStatusHandler(ctx, clients.OS.MachineRegistration(), "Ready", "machine-registration",
h.OnChange)
}
func (h *handler) OnChange(obj *v1.MachineRegistration, status v1.MachineRegistrationStatus) (v1.MachineRegistrationStatus, error) {
serverURL, err := h.serverURL()
if err != nil {
return status, err
}
if status.RegistrationToken == "" {
status.RegistrationToken, err = randomtoken.Generate()
if err != nil {
return status, err
}
}
status.RegistrationURL = serverURL + "/v1-rancheros/registration/" + status.RegistrationToken
return status, nil
}
func (h *handler) serverURL() (string, error) {
setting, err := h.settingsCache.Get("server-url")
if err != nil {
return "", err
}
if setting.Value == "" {
return "", fmt.Errorf("server-url is not set")
}
return setting.Value, nil
}

78
pkg/dmidecode/decode.go Normal file
View File

@@ -0,0 +1,78 @@
package dmidecode
import (
"bufio"
"bytes"
"fmt"
"io"
"os/exec"
"strings"
values "github.com/rancher/wrangler/pkg/data"
"github.com/rancher/wrangler/pkg/kv"
)
func Decode() (map[string]interface{}, error) {
buf := &bytes.Buffer{}
cmd := exec.Command("dmidecode")
cmd.Stdout = buf
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("looking up SMBIOS tables (using dmidecode): %w", err)
}
return dmiOutputToMap(buf), nil
}
func dmiOutputToMap(buf io.Reader) map[string]interface{} {
var (
result = map[string]interface{}{}
scanner = bufio.NewScanner(buf)
start = false
lastKey []string
stopLines = map[string]bool{
"OEM-specific Type": true,
"End Of Table": true,
}
)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "Handle ") {
start = true
continue
} else if strings.TrimSpace(line) == "" || !start || stopLines[line] {
start = false
continue
}
var key []string
for strings.HasPrefix(line, "\t") {
line = strings.TrimPrefix(line, "\t")
if len(lastKey) > len(key) {
key = append(key, lastKey[len(key)])
}
}
name, value := kv.Split(line, ": ")
key = append(key, name)
if strings.TrimSpace(value) != "" || strings.Contains(line, ":") {
values.PutValue(result, value, key...)
} else if len(key) > 1 {
parentKey := key[:len(key)-1]
parentValue := values.GetValueN(result, parentKey...)
if parentSlice, ok := parentValue.([]interface{}); ok {
parentValue = append(parentSlice, name)
} else {
parentValue = []interface{}{name}
}
values.PutValue(result, parentValue, parentKey...)
} else {
values.PutValue(result, map[string]interface{}{}, key...)
}
lastKey = key
}
return result
}

View File

@@ -0,0 +1,945 @@
package dmidecode
import (
"bytes"
"encoding/json"
"testing"
"gotest.tools/assert"
)
var testInput = `
# dmidecode 3.2
Getting SMBIOS data from sysfs.
SMBIOS 3.1.1 present.
Table at 0xACE76000.
Handle 0x0000, DMI type 222, 14 bytes
OEM-specific Type
Header and Data:
DE 0E 00 00 01 99 00 03 10 01 20 02 30 03
Strings:
Memory Init Complete
End of DXE Phase
BIOS Boot Complete
Handle 0x0001, DMI type 14, 8 bytes
Group Associations
Name: Intel(R) Silicon View Technology
Items: 1
0x0000 (OEM-specific)
Handle 0x0002, DMI type 134, 13 bytes
OEM-specific Type
Header and Data:
86 0D 02 00 21 02 20 20 00 00 00 00 00
Handle 0x0003, DMI type 16, 23 bytes
Physical Memory Array
Location: System Board Or Motherboard
Use: System Memory
Error Correction Type: None
Maximum Capacity: 64 GB
Error Information Handle: Not Provided
Number Of Devices: 4
Handle 0x0004, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0003
Error Information Handle: Not Provided
Total Width: 64 bits
Data Width: 64 bits
Size: 32 GB
Form Factor: SODIMM
Set: None
Locator: ChannelA-DIMM0
Bank Locator: BANK 0
Type: DDR4
Type Detail: Synchronous
Speed: 2667 MT/s
Manufacturer: Micron
Serial Number: 25CD9D0D
Asset Tag: None
Part Number: 16ATF4G64HZ-2G6B2
Rank: 2
Configured Memory Speed: 2667 MT/s
Minimum Voltage: Unknown
Maximum Voltage: Unknown
Configured Voltage: 1.2 V
Handle 0x0005, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0003
Error Information Handle: Not Provided
Total Width: 64 bits
Data Width: 64 bits
Size: 32 GB
Form Factor: SODIMM
Set: None
Locator: ChannelB-DIMM0
Bank Locator: BANK 2
Type: DDR4
Type Detail: Synchronous
Speed: 2667 MT/s
Manufacturer: Micron
Serial Number: XXXXXXXX
Asset Tag: None
Part Number: 16ATF4G64HZ-2G6B2
Rank: 2
Configured Memory Speed: 2667 MT/s
Minimum Voltage: Unknown
Maximum Voltage: Unknown
Configured Voltage: 1.2 V
Handle 0x0006, DMI type 19, 31 bytes
Memory Array Mapped Address
Starting Address: 0x00000000000
Ending Address: 0x00FFFFFFFFF
Range Size: 64 GB
Physical Array Handle: 0x0003
Partition Width: 2
Handle 0x0007, DMI type 221, 12 bytes
OEM-specific Type
Header and Data:
DD 0C 07 00 01 01 00 02 00 00 BD 10
Strings:
BIOS Guard
Handle 0x0008, DMI type 221, 26 bytes
OEM-specific Type
Header and Data:
DD 1A 08 00 03 01 00 07 00 68 40 00 02 00 00 00
00 D6 00 03 00 01 06 00 00 00
Strings:
Reference Code - CPU
uCode Version
TXT ACM version
Handle 0x0009, DMI type 221, 26 bytes
OEM-specific Type
Header and Data:
DD 1A 09 00 03 01 00 07 00 68 40 00 02 00 0C 00
00 0A 00 03 04 0C 00 46 74 06
Strings:
Reference Code - ME
MEBx version
ME Firmware Version
Corporate SKU
Handle 0x000A, DMI type 221, 82 bytes
OEM-specific Type
Header and Data:
DD 52 0A 00 0B 01 00 07 00 68 40 00 02 03 FF FF
FF FF FF 04 00 FF FF FF 10 00 05 00 FF FF FF 10
00 06 00 02 0A 00 00 00 07 00 02 00 00 00 00 08
00 09 00 00 00 00 09 00 0A 00 00 00 00 0A 00 07
00 00 00 00 0B 00 06 00 00 00 00 0C 00 07 00 00
00 00
Strings:
Reference Code - CNL PCH
PCH-CRID Status
Disabled
PCH-CRID Original Value
PCH-CRID New Value
OPROM - RST - RAID
CNL PCH H A0 Hsio Version
CNL PCH H Ax Hsio Version
CNL PCH H Bx Hsio Version
CNL PCH LP B0 Hsio Version
CNL PCH LP Bx Hsio Version
CNL PCH LP Dx Hsio Version
Handle 0x000B, DMI type 221, 54 bytes
OEM-specific Type
Header and Data:
DD 36 0B 00 07 01 00 07 00 68 40 00 02 00 00 07
01 6E 00 03 00 07 00 68 40 00 04 05 FF FF FF FF
FF 06 00 00 00 00 0D 00 07 00 00 00 00 0D 00 08
00 FF FF FF FF FF
Strings:
Reference Code - SA - System Agent
Reference Code - MRC
SA - PCIe Version
SA-CRID Status
Enabled
SA-CRID Original Value
SA-CRID New Value
OPROM - VBIOS
Handle 0x000C, DMI type 221, 12 bytes
OEM-specific Type
Header and Data:
DD 0C 0C 00 01 01 00 04 00 00 00 00
Strings:
FSP Binary Version
Handle 0x000D, DMI type 7, 27 bytes
Cache Information
Socket Designation: L1 Cache
Configuration: Enabled, Not Socketed, Level 1
Operational Mode: Write Back
Location: Internal
Installed Size: 512 kB
Maximum Size: 512 kB
Supported SRAM Types:
Synchronous
Installed SRAM Type: Synchronous
Speed: Unknown
Error Correction Type: Parity
System Type: Unified
Associativity: 8-way Set-associative
Handle 0x000E, DMI type 7, 27 bytes
Cache Information
Socket Designation: L2 Cache
Configuration: Enabled, Not Socketed, Level 2
Operational Mode: Write Back
Location: Internal
Installed Size: 2048 kB
Maximum Size: 2048 kB
Supported SRAM Types:
Synchronous
Installed SRAM Type: Synchronous
Speed: Unknown
Error Correction Type: Single-bit ECC
System Type: Unified
Associativity: 4-way Set-associative
Handle 0x000F, DMI type 7, 27 bytes
Cache Information
Socket Designation: L3 Cache
Configuration: Enabled, Not Socketed, Level 3
Operational Mode: Write Back
Location: Internal
Installed Size: 16384 kB
Maximum Size: 16384 kB
Supported SRAM Types:
Synchronous
Installed SRAM Type: Synchronous
Speed: Unknown
Error Correction Type: Multi-bit ECC
System Type: Unified
Associativity: 16-way Set-associative
Handle 0x0010, DMI type 4, 48 bytes
Processor Information
Socket Designation: U3E1
Type: Central Processor
Family: Core i9
Manufacturer: Intel(R) Corporation
ID: ED 06 09 00 FF FB EB BF
Signature: Type 0, Family 6, Model 158, Stepping 13
Flags:
FPU (Floating-point unit on-chip)
VME (Virtual mode extension)
DE (Debugging extension)
PSE (Page size extension)
TSC (Time stamp counter)
MSR (Model specific registers)
PAE (Physical address extension)
MCE (Machine check exception)
CX8 (CMPXCHG8 instruction supported)
APIC (On-chip APIC hardware supported)
SEP (Fast system call)
MTRR (Memory type range registers)
PGE (Page global enable)
MCA (Machine check architecture)
CMOV (Conditional move instruction supported)
PAT (Page attribute table)
PSE-36 (36-bit page size extension)
CLFSH (CLFLUSH instruction supported)
DS (Debug store)
ACPI (ACPI supported)
MMX (MMX technology supported)
FXSR (FXSAVE and FXSTOR instructions supported)
SSE (Streaming SIMD extensions)
SSE2 (Streaming SIMD extensions 2)
SS (Self-snoop)
HTT (Multi-threading)
TM (Thermal monitor supported)
PBE (Pending break enabled)
Version: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
Voltage: 0.8 V
External Clock: 100 MHz
Max Speed: 2300 MHz
Current Speed: 2300 MHz
Status: Populated, Enabled
Upgrade: Socket BGA1440
L1 Cache Handle: 0x000D
L2 Cache Handle: 0x000E
L3 Cache Handle: 0x000F
Serial Number: None
Asset Tag: None
Part Number: None
Core Count: 8
Core Enabled: 8
Thread Count: 16
Characteristics:
64-bit capable
Multi-Core
Hardware Thread
Execute Protection
Enhanced Virtualization
Power/Performance Control
Handle 0x0011, DMI type 0, 26 bytes
BIOS Information
Vendor: LENOVO
Version: N2OET47W (1.34 )
Release Date: 08/06/2020
Address: 0xE0000
Runtime Size: 128 kB
ROM Size: 32 MB
Characteristics:
PCI is supported
PNP is supported
BIOS is upgradeable
BIOS shadowing is allowed
Boot from CD is supported
Selectable boot is supported
EDD is supported
3.5"/720 kB floppy services are supported (int 13h)
Print screen service is supported (int 5h)
8042 keyboard services are supported (int 9h)
Serial services are supported (int 14h)
Printer services are supported (int 17h)
CGA/mono video services are supported (int 10h)
ACPI is supported
USB legacy is supported
BIOS boot specification is supported
Targeted content distribution is supported
UEFI is supported
BIOS Revision: 1.34
Firmware Revision: 1.23
Handle 0x0012, DMI type 1, 27 bytes
System Information
Manufacturer: LENOVO
Product Name: 20QTS00Y00
Version: ThinkPad P1 Gen 2
Serial Number: XXXXXXXX
UUID: 069a9e4c-2eec-11b2-a85c-e9c6a8e86998
Wake-up Type: Power Switch
SKU Number: LENOVO_MT_20QT_BU_Think_FM_ThinkPad P1 Gen 2
Family: ThinkPad P1 Gen 2
Handle 0x0013, DMI type 2, 15 bytes
Base Board Information
Manufacturer: LENOVO
Product Name: 20QTS00Y00
Version: SDK0T08861 WIN
Serial Number: XXXXXXXX02W
Asset Tag: Not Available
Features:
Board is a hosting board
Board is replaceable
Location In Chassis: Not Available
Chassis Handle: 0x0000
Type: Motherboard
Contained Object Handles: 0
Handle 0x0014, DMI type 3, 22 bytes
Chassis Information
Manufacturer: LENOVO
Type: Notebook
Lock: Not Present
Version: None
Serial Number: XXXXXXXX
Asset Tag: No Asset Information
Boot-up State: Unknown
Power Supply State: Unknown
Thermal State: Unknown
Security Status: Unknown
OEM Information: 0x00000000
Height: Unspecified
Number Of Power Cords: Unspecified
Contained Elements: 0
SKU Number: Not Specified
Handle 0x0015, DMI type 8, 9 bytes
Port Connector Information
Internal Reference Designator: Not Available
Internal Connector Type: None
External Reference Designator: USB 1
External Connector Type: Access Bus (USB)
Port Type: USB
Handle 0x0016, DMI type 8, 9 bytes
Port Connector Information
Internal Reference Designator: Not Available
Internal Connector Type: None
External Reference Designator: USB 2
External Connector Type: Access Bus (USB)
Port Type: USB
Handle 0x0017, DMI type 8, 9 bytes
Port Connector Information
Internal Reference Designator: Not Available
Internal Connector Type: None
External Reference Designator: USB 3
External Connector Type: Access Bus (USB)
Port Type: USB
Handle 0x0018, DMI type 8, 9 bytes
Port Connector Information
Internal Reference Designator: Not Available
Internal Connector Type: None
External Reference Designator: USB 4
External Connector Type: Access Bus (USB)
Port Type: USB
Handle 0x0019, DMI type 126, 9 bytes
Inactive
Handle 0x001A, DMI type 126, 9 bytes
Inactive
Handle 0x001B, DMI type 126, 9 bytes
Inactive
Handle 0x001C, DMI type 126, 9 bytes
Inactive
Handle 0x001D, DMI type 126, 9 bytes
Inactive
Handle 0x001E, DMI type 8, 9 bytes
Port Connector Information
Internal Reference Designator: Not Available
Internal Connector Type: None
External Reference Designator: Ethernet
External Connector Type: RJ-45
Port Type: Network Port
Handle 0x001F, DMI type 126, 9 bytes
Inactive
Handle 0x0020, DMI type 126, 9 bytes
Inactive
Handle 0x0021, DMI type 8, 9 bytes
Port Connector Information
Internal Reference Designator: Not Available
Internal Connector Type: None
External Reference Designator: Hdmi1
External Connector Type: Other
Port Type: Video Port
Handle 0x0022, DMI type 126, 9 bytes
Inactive
Handle 0x0023, DMI type 126, 9 bytes
Inactive
Handle 0x0024, DMI type 126, 9 bytes
Inactive
Handle 0x0025, DMI type 126, 9 bytes
Inactive
Handle 0x0026, DMI type 8, 9 bytes
Port Connector Information
Internal Reference Designator: Not Available
Internal Connector Type: None
External Reference Designator: Headphone/Microphone Combo Jack1
External Connector Type: Mini Jack (headphones)
Port Type: Audio Port
Handle 0x0027, DMI type 126, 9 bytes
Inactive
Handle 0x0028, DMI type 9, 17 bytes
System Slot Information
Designation: Media Card Slot
Type: Other
Current Usage: Available
Length: Other
Characteristics:
Hot-plug devices are supported
Bus Address: 00ff:ff:1f.7
Handle 0x0029, DMI type 12, 5 bytes
System Configuration Options
Handle 0x002A, DMI type 13, 22 bytes
BIOS Language Information
Language Description Format: Abbreviated
Installable Languages: 1
en-US
Currently Installed Language: en-US
Handle 0x002B, DMI type 22, 26 bytes
Portable Battery
Location: Front
Manufacturer: SMP
Name: 01YU911
Design Capacity: 80400 mWh
Design Voltage: 15360 mV
SBDS Version: 03.01
Maximum Error: Unknown
SBDS Serial Number: 09E9
SBDS Manufacture Date: 2019-12-10
SBDS Chemistry: LiP
OEM-specific Information: 0x00000000
Handle 0x002C, DMI type 126, 26 bytes
Inactive
Handle 0x002D, DMI type 140, 15 bytes
OEM-specific Type
Header and Data:
8C 0F 2D 00 4C 45 4E 4F 56 4F 0B 09 01 01 02
Strings:
1.34
1.34
Handle 0x002E, DMI type 133, 5 bytes
OEM-specific Type
Header and Data:
85 05 2E 00 01
Strings:
KHOIHGIUCCHHII
Handle 0x002F, DMI type 135, 19 bytes
OEM-specific Type
Header and Data:
87 13 2F 00 54 50 07 02 42 41 59 20 49 2F 4F 20
04 00 00
Handle 0x0030, DMI type 130, 20 bytes
OEM-specific Type
Header and Data:
82 14 30 00 24 41 4D 54 01 00 01 00 01 A5 AF 02
C0 00 00 00
Handle 0x0031, DMI type 131, 64 bytes
OEM-specific Type
Header and Data:
83 40 31 00 35 00 00 00 0C 00 00 00 00 00 0A 00
F8 00 0E A3 00 00 00 00 09 C0 00 00 00 00 0C 00
74 06 46 00 00 00 00 00 FE 00 BB 15 00 00 00 00
00 00 00 00 26 00 00 00 76 50 72 6F 00 00 00 00
Handle 0x0032, DMI type 24, 5 bytes
Hardware Security
Power-On Password Status: Disabled
Keyboard Password Status: Not Implemented
Administrator Password Status: Disabled
Front Panel Reset Status: Not Implemented
Handle 0x0033, DMI type 132, 7 bytes
OEM-specific Type
Header and Data:
84 07 33 00 01 C0 36
Handle 0x0034, DMI type 18, 23 bytes
32-bit Memory Error Information
Type: OK
Granularity: Unknown
Operation: Unknown
Vendor Syndrome: Unknown
Memory Array Address: Unknown
Device Address: Unknown
Resolution: Unknown
Handle 0x0035, DMI type 21, 7 bytes
Built-in Pointing Device
Type: Track Point
Interface: PS/2
Buttons: 3
Handle 0x0036, DMI type 21, 7 bytes
Built-in Pointing Device
Type: Touch Pad
Interface: PS/2
Buttons: 2
Handle 0x0037, DMI type 131, 22 bytes
ThinkVantage Technologies
Version: 1
Diagnostics: No
Handle 0x0038, DMI type 136, 6 bytes
OEM-specific Type
Header and Data:
88 06 38 00 5A 5A
Handle 0x0039, DMI type 15, 31 bytes
System Event Log
Area Length: 786 bytes
Header Start Offset: 0x0000
Header Length: 16 bytes
Data Start Offset: 0x0010
Access Method: General-purpose non-volatile data functions
Access Address: 0x00F0
Status: Valid, Not Full
Change Token: 0x00000030
Header Format: Type 1
Supported Log Type Descriptors: 4
Descriptor 1: POST error
Data Format 1: POST results bitmap
Descriptor 2: PCI system error
Data Format 2: None
Descriptor 3: System reconfigured
Data Format 3: None
Descriptor 4: Log area reset/cleared
Data Format 4: None
Handle 0x003A, DMI type 140, 19 bytes
OEM-specific Type
Header and Data:
8C 13 3A 00 4C 45 4E 4F 56 4F 0B 04 01 B2 00 4D
53 20 00
Handle 0x003B, DMI type 140, 19 bytes
OEM-specific Type
Header and Data:
8C 13 3B 00 4C 45 4E 4F 56 4F 0B 05 01 07 00 00
00 00 00
Handle 0x003C, DMI type 140, 23 bytes
OEM-specific Type
Header and Data:
8C 17 3C 00 4C 45 4E 4F 56 4F 0B 06 01 CB 06 51
74 01 60 00 00 00 00
Handle 0x003D, DMI type 14, 8 bytes
Group Associations
Name: $MEI
Items: 1
0x0000 (OEM-specific)
Handle 0x003E, DMI type 219, 106 bytes
OEM-specific Type
Header and Data:
DB 6A 3E 00 01 04 01 45 02 00 94 06 81 10 89 30
00 00 00 04 40 00 00 01 1F 00 00 C9 0A 40 C4 02
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF 03 00 00 00 80 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
Strings:
MEI1
MEI2
MEI3
MEI4
Handle 0x003F, DMI type 140, 15 bytes
ThinkPad Embedded Controller Program
Version ID: N2OHT36W
Release Date: 05/07/2020
Handle 0x0040, DMI type 140, 43 bytes
OEM-specific Type
Header and Data:
8C 2B 40 00 4C 45 4E 4F 56 4F 0B 08 01 03 21 08
05 18 39 03 21 05 19 18 04 03 21 05 19 17 56 03
21 05 19 17 54 03 21 05 19 17 48
Handle 0x0041, DMI type 135, 18 bytes
OEM-specific Type
Header and Data:
87 12 41 00 54 50 07 01 01 00 00 00 03 00 00 00
03 00
Handle 0xFEFF, DMI type 127, 4 bytes
End Of Table
`
var testOutput = `{
"32-bit Memory Error Information": {
"Device Address": "Unknown",
"Granularity": "Unknown",
"Memory Array Address": "Unknown",
"Operation": "Unknown",
"Resolution": "Unknown",
"Type": "OK",
"Vendor Syndrome": "Unknown"
},
"BIOS Information": {
"Address": "0xE0000",
"BIOS Revision": "1.34",
"Characteristics:": [
"PCI is supported",
"PNP is supported",
"BIOS is upgradeable",
"BIOS shadowing is allowed",
"Boot from CD is supported",
"Selectable boot is supported",
"EDD is supported",
"3.5\"/720 kB floppy services are supported (int 13h)",
"Print screen service is supported (int 5h)",
"8042 keyboard services are supported (int 9h)",
"Serial services are supported (int 14h)",
"Printer services are supported (int 17h)",
"CGA/mono video services are supported (int 10h)",
"ACPI is supported",
"USB legacy is supported",
"BIOS boot specification is supported",
"Targeted content distribution is supported",
"UEFI is supported"
],
"Firmware Revision": "1.23",
"ROM Size": "32 MB",
"Release Date": "08/06/2020",
"Runtime Size": "128 kB",
"Vendor": "LENOVO",
"Version": "N2OET47W (1.34 )"
},
"BIOS Language Information": {
"Currently Installed Language": "en-US",
"Installable Languages": [
"en-US"
],
"Language Description Format": "Abbreviated"
},
"Base Board Information": {
"Asset Tag": "Not Available",
"Chassis Handle": "0x0000",
"Contained Object Handles": "0",
"Features:": [
"Board is a hosting board",
"Board is replaceable"
],
"Location In Chassis": "Not Available",
"Manufacturer": "LENOVO",
"Product Name": "20QTS00Y00",
"Serial Number": "XXXXXXXX02W",
"Type": "Motherboard",
"Version": "SDK0T08861 WIN"
},
"Built-in Pointing Device": {
"Buttons": "2",
"Interface": "PS/2",
"Type": "Touch Pad"
},
"Cache Information": {
"Associativity": "16-way Set-associative",
"Configuration": "Enabled, Not Socketed, Level 3",
"Error Correction Type": "Multi-bit ECC",
"Installed SRAM Type": "Synchronous",
"Installed Size": "16384 kB",
"Location": "Internal",
"Maximum Size": "16384 kB",
"Operational Mode": "Write Back",
"Socket Designation": "L3 Cache",
"Speed": "Unknown",
"Supported SRAM Types:": [
"Synchronous"
],
"System Type": "Unified"
},
"Chassis Information": {
"Asset Tag": "No Asset Information",
"Boot-up State": "Unknown",
"Contained Elements": "0",
"Height": "Unspecified",
"Lock": "Not Present",
"Manufacturer": "LENOVO",
"Number Of Power Cords": "Unspecified",
"OEM Information": "0x00000000",
"Power Supply State": "Unknown",
"SKU Number": "Not Specified",
"Security Status": "Unknown",
"Serial Number": "XXXXXXXX",
"Thermal State": "Unknown",
"Type": "Notebook",
"Version": "None"
},
"Group Associations": {
"Items": [
"0x0000 (OEM-specific)"
],
"Name": "$MEI"
},
"Hardware Security": {
"Administrator Password Status": "Disabled",
"Front Panel Reset Status": "Not Implemented",
"Keyboard Password Status": "Not Implemented",
"Power-On Password Status": "Disabled"
},
"Inactive": {},
"Memory Array Mapped Address": {
"Ending Address": "0x00FFFFFFFFF",
"Partition Width": "2",
"Physical Array Handle": "0x0003",
"Range Size": "64 GB",
"Starting Address": "0x00000000000"
},
"Memory Device": {
"Array Handle": "0x0003",
"Asset Tag": "None",
"Bank Locator": "BANK 2",
"Configured Memory Speed": "2667 MT/s",
"Configured Voltage": "1.2 V",
"Data Width": "64 bits",
"Error Information Handle": "Not Provided",
"Form Factor": "SODIMM",
"Locator": "ChannelB-DIMM0",
"Manufacturer": "Micron",
"Maximum Voltage": "Unknown",
"Minimum Voltage": "Unknown",
"Part Number": "16ATF4G64HZ-2G6B2",
"Rank": "2",
"Serial Number": "XXXXXXXX",
"Set": "None",
"Size": "32 GB",
"Speed": "2667 MT/s",
"Total Width": "64 bits",
"Type": "DDR4",
"Type Detail": "Synchronous"
},
"Physical Memory Array": {
"Error Correction Type": "None",
"Error Information Handle": "Not Provided",
"Location": "System Board Or Motherboard",
"Maximum Capacity": "64 GB",
"Number Of Devices": "4",
"Use": "System Memory"
},
"Port Connector Information": {
"External Connector Type": "Mini Jack (headphones)",
"External Reference Designator": "Headphone/Microphone Combo Jack1",
"Internal Connector Type": "None",
"Internal Reference Designator": "Not Available",
"Port Type": "Audio Port"
},
"Portable Battery": {
"Design Capacity": "80400 mWh",
"Design Voltage": "15360 mV",
"Location": "Front",
"Manufacturer": "SMP",
"Maximum Error": "Unknown",
"Name": "01YU911",
"OEM-specific Information": "0x00000000",
"SBDS Chemistry": "LiP",
"SBDS Manufacture Date": "2019-12-10",
"SBDS Serial Number": "09E9",
"SBDS Version": "03.01"
},
"Processor Information": {
"Asset Tag": "None",
"Characteristics:": [
"64-bit capable",
"Multi-Core",
"Hardware Thread",
"Execute Protection",
"Enhanced Virtualization",
"Power/Performance Control"
],
"Core Count": "8",
"Core Enabled": "8",
"Current Speed": "2300 MHz",
"External Clock": "100 MHz",
"Family": "Core i9",
"Flags:": [
"FPU (Floating-point unit on-chip)",
"VME (Virtual mode extension)",
"DE (Debugging extension)",
"PSE (Page size extension)",
"TSC (Time stamp counter)",
"MSR (Model specific registers)",
"PAE (Physical address extension)",
"MCE (Machine check exception)",
"CX8 (CMPXCHG8 instruction supported)",
"APIC (On-chip APIC hardware supported)",
"SEP (Fast system call)",
"MTRR (Memory type range registers)",
"PGE (Page global enable)",
"MCA (Machine check architecture)",
"CMOV (Conditional move instruction supported)",
"PAT (Page attribute table)",
"PSE-36 (36-bit page size extension)",
"CLFSH (CLFLUSH instruction supported)",
"DS (Debug store)",
"ACPI (ACPI supported)",
"MMX (MMX technology supported)",
"FXSR (FXSAVE and FXSTOR instructions supported)",
"SSE (Streaming SIMD extensions)",
"SSE2 (Streaming SIMD extensions 2)",
"SS (Self-snoop)",
"HTT (Multi-threading)",
"TM (Thermal monitor supported)",
"PBE (Pending break enabled)"
],
"ID": "ED 06 09 00 FF FB EB BF",
"L1 Cache Handle": "0x000D",
"L2 Cache Handle": "0x000E",
"L3 Cache Handle": "0x000F",
"Manufacturer": "Intel(R) Corporation",
"Max Speed": "2300 MHz",
"Part Number": "None",
"Serial Number": "None",
"Signature": "Type 0, Family 6, Model 158, Stepping 13",
"Socket Designation": "U3E1",
"Status": "Populated, Enabled",
"Thread Count": "16",
"Type": "Central Processor",
"Upgrade": "Socket BGA1440",
"Version": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
"Voltage": "0.8 V"
},
"System Configuration Options": {},
"System Event Log": {
"Access Address": "0x00F0",
"Access Method": "General-purpose non-volatile data functions",
"Area Length": "786 bytes",
"Change Token": "0x00000030",
"Data Format 1": "POST results bitmap",
"Data Format 2": "None",
"Data Format 3": "None",
"Data Format 4": "None",
"Data Start Offset": "0x0010",
"Descriptor 1": "POST error",
"Descriptor 2": "PCI system error",
"Descriptor 3": "System reconfigured",
"Descriptor 4": "Log area reset/cleared",
"Header Format": "Type 1",
"Header Length": "16 bytes",
"Header Start Offset": "0x0000",
"Status": "Valid, Not Full",
"Supported Log Type Descriptors": "4"
},
"System Information": {
"Family": "ThinkPad P1 Gen 2",
"Manufacturer": "LENOVO",
"Product Name": "20QTS00Y00",
"SKU Number": "LENOVO_MT_20QT_BU_Think_FM_ThinkPad P1 Gen 2",
"Serial Number": "XXXXXXXX",
"UUID": "069a9e4c-2eec-11b2-a85c-e9c6a8e86998",
"Version": "ThinkPad P1 Gen 2",
"Wake-up Type": "Power Switch"
},
"System Slot Information": {
"Bus Address": "00ff:ff:1f.7",
"Characteristics:": [
"Hot-plug devices are supported"
],
"Current Usage": "Available",
"Designation": "Media Card Slot",
"Length": "Other",
"Type": "Other"
},
"ThinkPad Embedded Controller Program": {
"Release Date": "05/07/2020",
"Version ID": "N2OHT36W"
},
"ThinkVantage Technologies": {
"Diagnostics": "No",
"Version": "1"
}
}
`
func TestParse(t *testing.T) {
output := dmiOutputToMap(bytes.NewBufferString(testInput))
outputBuffer := bytes.NewBuffer(nil)
enc := json.NewEncoder(outputBuffer)
enc.SetIndent("", " ")
if err := enc.Encode(output); err != nil {
t.Fatal(err)
}
assert.Equal(t, testOutput, outputBuffer.String())
}

View File

@@ -31,6 +31,7 @@ func init() {
type Interface interface {
MachineInventory() MachineInventoryController
MachineRegistration() MachineRegistrationController
ManagedOSImage() ManagedOSImageController
}
@@ -47,6 +48,9 @@ type version struct {
func (c *version) MachineInventory() MachineInventoryController {
return NewMachineInventoryController(schema.GroupVersionKind{Group: "rancheros.cattle.io", Version: "v1", Kind: "MachineInventory"}, "machineinventories", true, c.controllerFactory)
}
func (c *version) MachineRegistration() MachineRegistrationController {
return NewMachineRegistrationController(schema.GroupVersionKind{Group: "rancheros.cattle.io", Version: "v1", Kind: "MachineRegistration"}, "machineregistrations", true, c.controllerFactory)
}
func (c *version) ManagedOSImage() ManagedOSImageController {
return NewManagedOSImageController(schema.GroupVersionKind{Group: "rancheros.cattle.io", Version: "v1", Kind: "ManagedOSImage"}, "managedosimages", true, c.controllerFactory)
}

View File

@@ -0,0 +1,376 @@
/*
Copyright 2021 Rancher Labs, Inc.
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.
*/
// Code generated by main. DO NOT EDIT.
package v1
import (
"context"
"time"
"github.com/rancher/lasso/pkg/client"
"github.com/rancher/lasso/pkg/controller"
v1 "github.com/rancher/os2/pkg/apis/rancheros.cattle.io/v1"
"github.com/rancher/wrangler/pkg/apply"
"github.com/rancher/wrangler/pkg/condition"
"github.com/rancher/wrangler/pkg/generic"
"github.com/rancher/wrangler/pkg/kv"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/cache"
)
type MachineRegistrationHandler func(string, *v1.MachineRegistration) (*v1.MachineRegistration, error)
type MachineRegistrationController interface {
generic.ControllerMeta
MachineRegistrationClient
OnChange(ctx context.Context, name string, sync MachineRegistrationHandler)
OnRemove(ctx context.Context, name string, sync MachineRegistrationHandler)
Enqueue(namespace, name string)
EnqueueAfter(namespace, name string, duration time.Duration)
Cache() MachineRegistrationCache
}
type MachineRegistrationClient interface {
Create(*v1.MachineRegistration) (*v1.MachineRegistration, error)
Update(*v1.MachineRegistration) (*v1.MachineRegistration, error)
UpdateStatus(*v1.MachineRegistration) (*v1.MachineRegistration, error)
Delete(namespace, name string, options *metav1.DeleteOptions) error
Get(namespace, name string, options metav1.GetOptions) (*v1.MachineRegistration, error)
List(namespace string, opts metav1.ListOptions) (*v1.MachineRegistrationList, error)
Watch(namespace string, opts metav1.ListOptions) (watch.Interface, error)
Patch(namespace, name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.MachineRegistration, err error)
}
type MachineRegistrationCache interface {
Get(namespace, name string) (*v1.MachineRegistration, error)
List(namespace string, selector labels.Selector) ([]*v1.MachineRegistration, error)
AddIndexer(indexName string, indexer MachineRegistrationIndexer)
GetByIndex(indexName, key string) ([]*v1.MachineRegistration, error)
}
type MachineRegistrationIndexer func(obj *v1.MachineRegistration) ([]string, error)
type machineRegistrationController struct {
controller controller.SharedController
client *client.Client
gvk schema.GroupVersionKind
groupResource schema.GroupResource
}
func NewMachineRegistrationController(gvk schema.GroupVersionKind, resource string, namespaced bool, controller controller.SharedControllerFactory) MachineRegistrationController {
c := controller.ForResourceKind(gvk.GroupVersion().WithResource(resource), gvk.Kind, namespaced)
return &machineRegistrationController{
controller: c,
client: c.Client(),
gvk: gvk,
groupResource: schema.GroupResource{
Group: gvk.Group,
Resource: resource,
},
}
}
func FromMachineRegistrationHandlerToHandler(sync MachineRegistrationHandler) generic.Handler {
return func(key string, obj runtime.Object) (ret runtime.Object, err error) {
var v *v1.MachineRegistration
if obj == nil {
v, err = sync(key, nil)
} else {
v, err = sync(key, obj.(*v1.MachineRegistration))
}
if v == nil {
return nil, err
}
return v, err
}
}
func (c *machineRegistrationController) Updater() generic.Updater {
return func(obj runtime.Object) (runtime.Object, error) {
newObj, err := c.Update(obj.(*v1.MachineRegistration))
if newObj == nil {
return nil, err
}
return newObj, err
}
}
func UpdateMachineRegistrationDeepCopyOnChange(client MachineRegistrationClient, obj *v1.MachineRegistration, handler func(obj *v1.MachineRegistration) (*v1.MachineRegistration, error)) (*v1.MachineRegistration, error) {
if obj == nil {
return obj, nil
}
copyObj := obj.DeepCopy()
newObj, err := handler(copyObj)
if newObj != nil {
copyObj = newObj
}
if obj.ResourceVersion == copyObj.ResourceVersion && !equality.Semantic.DeepEqual(obj, copyObj) {
return client.Update(copyObj)
}
return copyObj, err
}
func (c *machineRegistrationController) AddGenericHandler(ctx context.Context, name string, handler generic.Handler) {
c.controller.RegisterHandler(ctx, name, controller.SharedControllerHandlerFunc(handler))
}
func (c *machineRegistrationController) AddGenericRemoveHandler(ctx context.Context, name string, handler generic.Handler) {
c.AddGenericHandler(ctx, name, generic.NewRemoveHandler(name, c.Updater(), handler))
}
func (c *machineRegistrationController) OnChange(ctx context.Context, name string, sync MachineRegistrationHandler) {
c.AddGenericHandler(ctx, name, FromMachineRegistrationHandlerToHandler(sync))
}
func (c *machineRegistrationController) OnRemove(ctx context.Context, name string, sync MachineRegistrationHandler) {
c.AddGenericHandler(ctx, name, generic.NewRemoveHandler(name, c.Updater(), FromMachineRegistrationHandlerToHandler(sync)))
}
func (c *machineRegistrationController) Enqueue(namespace, name string) {
c.controller.Enqueue(namespace, name)
}
func (c *machineRegistrationController) EnqueueAfter(namespace, name string, duration time.Duration) {
c.controller.EnqueueAfter(namespace, name, duration)
}
func (c *machineRegistrationController) Informer() cache.SharedIndexInformer {
return c.controller.Informer()
}
func (c *machineRegistrationController) GroupVersionKind() schema.GroupVersionKind {
return c.gvk
}
func (c *machineRegistrationController) Cache() MachineRegistrationCache {
return &machineRegistrationCache{
indexer: c.Informer().GetIndexer(),
resource: c.groupResource,
}
}
func (c *machineRegistrationController) Create(obj *v1.MachineRegistration) (*v1.MachineRegistration, error) {
result := &v1.MachineRegistration{}
return result, c.client.Create(context.TODO(), obj.Namespace, obj, result, metav1.CreateOptions{})
}
func (c *machineRegistrationController) Update(obj *v1.MachineRegistration) (*v1.MachineRegistration, error) {
result := &v1.MachineRegistration{}
return result, c.client.Update(context.TODO(), obj.Namespace, obj, result, metav1.UpdateOptions{})
}
func (c *machineRegistrationController) UpdateStatus(obj *v1.MachineRegistration) (*v1.MachineRegistration, error) {
result := &v1.MachineRegistration{}
return result, c.client.UpdateStatus(context.TODO(), obj.Namespace, obj, result, metav1.UpdateOptions{})
}
func (c *machineRegistrationController) Delete(namespace, name string, options *metav1.DeleteOptions) error {
if options == nil {
options = &metav1.DeleteOptions{}
}
return c.client.Delete(context.TODO(), namespace, name, *options)
}
func (c *machineRegistrationController) Get(namespace, name string, options metav1.GetOptions) (*v1.MachineRegistration, error) {
result := &v1.MachineRegistration{}
return result, c.client.Get(context.TODO(), namespace, name, result, options)
}
func (c *machineRegistrationController) List(namespace string, opts metav1.ListOptions) (*v1.MachineRegistrationList, error) {
result := &v1.MachineRegistrationList{}
return result, c.client.List(context.TODO(), namespace, result, opts)
}
func (c *machineRegistrationController) Watch(namespace string, opts metav1.ListOptions) (watch.Interface, error) {
return c.client.Watch(context.TODO(), namespace, opts)
}
func (c *machineRegistrationController) Patch(namespace, name string, pt types.PatchType, data []byte, subresources ...string) (*v1.MachineRegistration, error) {
result := &v1.MachineRegistration{}
return result, c.client.Patch(context.TODO(), namespace, name, pt, data, result, metav1.PatchOptions{}, subresources...)
}
type machineRegistrationCache struct {
indexer cache.Indexer
resource schema.GroupResource
}
func (c *machineRegistrationCache) Get(namespace, name string) (*v1.MachineRegistration, error) {
obj, exists, err := c.indexer.GetByKey(namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(c.resource, name)
}
return obj.(*v1.MachineRegistration), nil
}
func (c *machineRegistrationCache) List(namespace string, selector labels.Selector) (ret []*v1.MachineRegistration, err error) {
err = cache.ListAllByNamespace(c.indexer, namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1.MachineRegistration))
})
return ret, err
}
func (c *machineRegistrationCache) AddIndexer(indexName string, indexer MachineRegistrationIndexer) {
utilruntime.Must(c.indexer.AddIndexers(map[string]cache.IndexFunc{
indexName: func(obj interface{}) (strings []string, e error) {
return indexer(obj.(*v1.MachineRegistration))
},
}))
}
func (c *machineRegistrationCache) GetByIndex(indexName, key string) (result []*v1.MachineRegistration, err error) {
objs, err := c.indexer.ByIndex(indexName, key)
if err != nil {
return nil, err
}
result = make([]*v1.MachineRegistration, 0, len(objs))
for _, obj := range objs {
result = append(result, obj.(*v1.MachineRegistration))
}
return result, nil
}
type MachineRegistrationStatusHandler func(obj *v1.MachineRegistration, status v1.MachineRegistrationStatus) (v1.MachineRegistrationStatus, error)
type MachineRegistrationGeneratingHandler func(obj *v1.MachineRegistration, status v1.MachineRegistrationStatus) ([]runtime.Object, v1.MachineRegistrationStatus, error)
func RegisterMachineRegistrationStatusHandler(ctx context.Context, controller MachineRegistrationController, condition condition.Cond, name string, handler MachineRegistrationStatusHandler) {
statusHandler := &machineRegistrationStatusHandler{
client: controller,
condition: condition,
handler: handler,
}
controller.AddGenericHandler(ctx, name, FromMachineRegistrationHandlerToHandler(statusHandler.sync))
}
func RegisterMachineRegistrationGeneratingHandler(ctx context.Context, controller MachineRegistrationController, apply apply.Apply,
condition condition.Cond, name string, handler MachineRegistrationGeneratingHandler, opts *generic.GeneratingHandlerOptions) {
statusHandler := &machineRegistrationGeneratingHandler{
MachineRegistrationGeneratingHandler: handler,
apply: apply,
name: name,
gvk: controller.GroupVersionKind(),
}
if opts != nil {
statusHandler.opts = *opts
}
controller.OnChange(ctx, name, statusHandler.Remove)
RegisterMachineRegistrationStatusHandler(ctx, controller, condition, name, statusHandler.Handle)
}
type machineRegistrationStatusHandler struct {
client MachineRegistrationClient
condition condition.Cond
handler MachineRegistrationStatusHandler
}
func (a *machineRegistrationStatusHandler) sync(key string, obj *v1.MachineRegistration) (*v1.MachineRegistration, error) {
if obj == nil {
return obj, nil
}
origStatus := obj.Status.DeepCopy()
obj = obj.DeepCopy()
newStatus, err := a.handler(obj, obj.Status)
if err != nil {
// Revert to old status on error
newStatus = *origStatus.DeepCopy()
}
if a.condition != "" {
if errors.IsConflict(err) {
a.condition.SetError(&newStatus, "", nil)
} else {
a.condition.SetError(&newStatus, "", err)
}
}
if !equality.Semantic.DeepEqual(origStatus, &newStatus) {
if a.condition != "" {
// Since status has changed, update the lastUpdatedTime
a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339))
}
var newErr error
obj.Status = newStatus
newObj, newErr := a.client.UpdateStatus(obj)
if err == nil {
err = newErr
}
if newErr == nil {
obj = newObj
}
}
return obj, err
}
type machineRegistrationGeneratingHandler struct {
MachineRegistrationGeneratingHandler
apply apply.Apply
opts generic.GeneratingHandlerOptions
gvk schema.GroupVersionKind
name string
}
func (a *machineRegistrationGeneratingHandler) Remove(key string, obj *v1.MachineRegistration) (*v1.MachineRegistration, error) {
if obj != nil {
return obj, nil
}
obj = &v1.MachineRegistration{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(a.gvk)
return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects()
}
func (a *machineRegistrationGeneratingHandler) Handle(obj *v1.MachineRegistration, status v1.MachineRegistrationStatus) (v1.MachineRegistrationStatus, error) {
if !obj.DeletionTimestamp.IsZero() {
return status, nil
}
objs, newStatus, err := a.MachineRegistrationGeneratingHandler(obj, status)
if err != nil {
return newStatus, err
}
return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects(objs...)
}

View File

@@ -11,16 +11,24 @@ import (
"sigs.k8s.io/yaml"
)
func Run(automatic bool, configFile string) error {
func Run(automatic bool, configFile string, powerOff bool, silent bool) error {
cfg, err := config.ReadConfig(configFile)
if err != nil {
return err
}
if powerOff {
cfg.RancherOS.Install.PowerOff = true
}
if automatic && !cfg.RancherOS.Install.Automatic {
return nil
}
if silent {
cfg.RancherOS.Install.Automatic = true
}
err = Ask(&cfg)
if err != nil {
return err

View File

@@ -7,6 +7,7 @@ import (
"github.com/rancher/os2/pkg/clients"
"github.com/rancher/os2/pkg/controllers/inventory"
"github.com/rancher/os2/pkg/controllers/managedos"
"github.com/rancher/os2/pkg/controllers/registration"
"github.com/rancher/os2/pkg/server"
"github.com/rancher/steve/pkg/aggregation"
"github.com/rancher/wrangler/pkg/crd"
@@ -39,6 +40,10 @@ func Run(ctx context.Context, namespace string) error {
SchemaObject: v1.MachineInventory{},
Status: true,
},
crd.CRD{
SchemaObject: v1.MachineRegistration{},
Status: true,
},
).BatchWait()
if err != nil {
logrus.Fatalf("Failed to create CRDs: %v", err)
@@ -46,6 +51,7 @@ func Run(ctx context.Context, namespace string) error {
managedos.Register(ctx, clients)
inventory.Register(ctx, clients)
registration.Register(ctx, clients)
aggregation.Watch(ctx, clients.Core.Secret(), namespace, "rancheros-operator", server.New(clients))
return clients.Start(ctx)

View File

@@ -4,6 +4,7 @@ import (
"crypto/hmac"
"crypto/sha512"
"encoding/base64"
"fmt"
"net/http"
"strings"
)
@@ -29,7 +30,7 @@ func (i *InventoryServer) cacerts(rw http.ResponseWriter, req *http.Request) {
if authorization != "" && nonce != "" {
crt, err := i.secretCache.GetByIndex(tokenHash, authorization)
if err == nil && len(crt) >= 0 {
if err == nil && len(crt) > 0 {
digest := hmac.New(sha512.New, crt[0].Data[tokenKey])
digest.Write([]byte(nonce))
digest.Write([]byte{0})
@@ -37,6 +38,14 @@ func (i *InventoryServer) cacerts(rw http.ResponseWriter, req *http.Request) {
digest.Write([]byte{0})
hash := digest.Sum(nil)
rw.Header().Set("X-Cattle-Hash", base64.StdEncoding.EncodeToString(hash))
} else if machines, err := i.machineCache.GetByIndex(tokenHash, authorization); len(machines) == 1 && err == nil {
digest := hmac.New(sha512.New, []byte(machines[0].Spec.TPMHash))
digest.Write([]byte(nonce))
digest.Write([]byte{0})
digest.Write(bytes)
digest.Write([]byte{0})
hash := digest.Sum(nil)
rw.Header().Set("X-Cattle-Hash", base64.StdEncoding.EncodeToString(hash))
}
}
@@ -50,5 +59,22 @@ func (i *InventoryServer) cacert() string {
if err != nil {
return ""
}
if setting.Value == "" {
setting, err = i.settingCache.Get("internal-cacerts")
if err != nil {
return ""
}
}
return setting.Value
}
func (i *InventoryServer) serverURL() (string, error) {
setting, err := i.settingCache.Get("server-url")
if err != nil {
return "", err
}
if setting.Value == "" {
return "", fmt.Errorf("server-url is not set")
}
return setting.Value, nil
}

182
pkg/server/register.go Normal file
View File

@@ -0,0 +1,182 @@
package server
import (
"encoding/base64"
"encoding/json"
"io"
"net/http"
"path"
"regexp"
"strings"
"github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
v1 "github.com/rancher/os2/pkg/apis/rancheros.cattle.io/v1"
values "github.com/rancher/wrangler/pkg/data"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const defaultName = "m-${System Information/Manufacturer}-${System Information/Product Name}-${System Information/Serial Number}-"
var (
sanitize = regexp.MustCompile("[^0-9a-zA-Z]")
doubleDash = regexp.MustCompile("--+")
start = regexp.MustCompile("^[a-zA-Z]")
)
func (i *InventoryServer) register(resp http.ResponseWriter, req *http.Request) {
machineInventory, machineRegister, data, err := i.buildResponse(req)
if err != nil {
http.Error(resp, err.Error(), http.StatusUnauthorized)
return
}
machine, writer, err := i.authMachine(resp, req, machineInventory.Namespace)
if err != nil {
http.Error(resp, err.Error(), http.StatusUnauthorized)
return
}
if writer == nil {
if err := i.sampleConfig(machineRegister, resp); err != nil {
http.Error(resp, "authorization required", http.StatusUnauthorized)
}
return
}
defer writer.Close()
if err := i.saveMachine(machine.Spec.TPMHash, machineInventory); err != nil {
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}
_, _ = writer.Write(data)
}
func (i *InventoryServer) sampleConfig(machineRegistration *v1.MachineRegistration, writer io.Writer) error {
_, err := writer.Write([]byte("#cloud-config\n"))
if err != nil {
return err
}
return yaml.NewEncoder(writer).Encode(map[string]interface{}{
"rancheros": map[string]interface{}{
"install": map[string]interface{}{
"registrationURL": machineRegistration.Status.RegistrationURL,
},
},
})
}
func (i *InventoryServer) saveMachine(tpmHash string, machineInventory *v1.MachineInventory) error {
machines, err := i.machineCache.GetByIndex(tpmHashIndex, tpmHash)
if err != nil || len(machines) > 0 {
return err
}
machineInventory.Spec.TPMHash = tpmHash
_, err = i.machineClient.Create(machineInventory)
return err
}
func buildName(data map[string]interface{}, name string) string {
str := name
result := &strings.Builder{}
for {
i := strings.Index(str, "${")
if i == -1 {
result.WriteString(str)
break
}
j := strings.Index(str[i:], "}")
if j == -1 {
result.WriteString(str)
break
}
result.WriteString(str[:i])
obj := values.GetValueN(data, strings.Split(str[i+2:j+i], "/")...)
if str, ok := obj.(string); ok {
result.WriteString(str)
}
str = str[j+i+1:]
}
resultStr := sanitize.ReplaceAllString(result.String(), "-")
resultStr = doubleDash.ReplaceAllString(resultStr, "-")
if !start.MatchString(resultStr) {
resultStr = "m" + resultStr
}
if len(resultStr) > 58 {
resultStr = resultStr[:58]
}
return strings.ToLower(resultStr)
}
func (i *InventoryServer) buildResponse(req *http.Request) (*v1.MachineInventory, *v1.MachineRegistration, []byte, error) {
token := path.Base(req.URL.Path)
smbios, err := getSMBios(req)
if err != nil {
return nil, nil, nil, err
}
machineRegisters, err := i.machineRegistrationCache.GetByIndex(registrationTokenIndex, token)
if apierrors.IsNotFound(err) || len(machineRegisters) != 1 {
if len(machineRegisters) > 1 {
logrus.Errorf("Multiple MachineRegistrations have the same token %s: %v", token, machineRegisters)
}
return nil, nil, nil, err
}
machineRegister := machineRegisters[0]
name := machineRegister.Spec.MachineName
if name == "" {
name = defaultName
}
installConfig := map[string]interface{}{}
if machineRegister.Spec.CloudConfig != nil && len(machineRegister.Spec.CloudConfig.Data) > 0 {
installConfig = values.MergeMapsConcatSlice(installConfig, machineRegister.Spec.CloudConfig.Data)
}
serverURL, err := i.serverURL()
if err != nil {
return nil, nil, nil, err
}
values.PutValue(installConfig, serverURL, "rancherd", "server")
values.PutValue(installConfig, "tpm://", "rancherd", "token")
values.PutValue(installConfig, true, "rancheros", "install", "automatic")
data, err := json.Marshal(installConfig)
if err != nil {
return nil, nil, nil, err
}
return &v1.MachineInventory{
ObjectMeta: metav1.ObjectMeta{
GenerateName: buildName(smbios, name),
Namespace: machineRegister.Namespace,
Labels: machineRegisters[0].Spec.MachineInventoryLabels,
Annotations: machineRegisters[0].Spec.MachineInventoryAnnotations,
},
Spec: v1.MachineInventorySpec{
SMBIOS: &v1alpha1.GenericMap{
Data: smbios,
},
},
}, machineRegister, data, nil
}
func getSMBios(req *http.Request) (map[string]interface{}, error) {
smbios := req.Header.Get("X-Cattle-Smbios")
if smbios == "" {
return nil, nil
}
smbiosData, err := base64.StdEncoding.DecodeString(smbios)
if err != nil {
return nil, err
}
data := map[string]interface{}{}
return data, json.Unmarshal(smbiosData, &data)
}

View File

@@ -0,0 +1,81 @@
package server
import (
"testing"
"gotest.tools/assert"
)
func TestBuildName(t *testing.T) {
data := map[string]interface{}{
"level1A": map[string]interface{}{
"level2A": "level2AValue",
"level2B": map[string]interface{}{
"level3A": "level3AValue",
},
},
"level1B": "level1BValue",
}
testCase := []struct {
Format string
Output string
}{
{
Format: "${level1B}",
Output: "level1bvalue",
},
{
Format: "${level1B",
Output: "m-level1b",
},
{
Format: "a${level1B",
Output: "a-level1b",
},
{
Format: "${}",
Output: "m",
},
{
Format: "${",
Output: "m-",
},
{
Format: "a${",
Output: "a-",
},
{
Format: "${level1A}",
Output: "m",
},
{
Format: "a${level1A}c",
Output: "ac",
},
{
Format: "a${level1A}",
Output: "a",
},
{
Format: "${level1A}c",
Output: "c",
},
{
Format: "a${level1A/level2A}c",
Output: "alevel2avaluec",
},
{
Format: "a${level1A/level2B/level3A}c",
Output: "alevel3avaluec",
},
{
Format: "a${level1A/level2B/level3A}c${level1B}",
Output: "alevel3avalueclevel1bvalue",
},
}
for _, testCase := range testCase {
assert.Equal(t, testCase.Output, buildName(data, testCase.Format))
}
}

86
pkg/server/secret.go Normal file
View File

@@ -0,0 +1,86 @@
package server
import (
"encoding/base64"
"fmt"
"io"
"net/http"
"strings"
v1 "github.com/rancher/os2/pkg/apis/rancheros.cattle.io/v1"
"github.com/rancher/os2/pkg/clients"
roscontrollers "github.com/rancher/os2/pkg/generated/controllers/rancheros.cattle.io/v1"
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
type sharedSecretAuth struct {
secretCache corecontrollers.SecretCache
machineCache roscontrollers.MachineInventoryCache
}
func newSharedSecretAuth(clients *clients.Clients) *sharedSecretAuth {
server := &sharedSecretAuth{
secretCache: clients.Core.Secret().Cache(),
machineCache: clients.OS.MachineInventory().Cache(),
}
server.secretCache.AddIndexer(tokenIndex, func(obj *corev1.Secret) ([]string, error) {
if string(obj.Type) != tokenType {
return nil, nil
}
t := obj.Data[tokenKey]
if len(t) == 0 {
return nil, nil
}
return []string{base64.StdEncoding.EncodeToString(t)}, nil
})
server.machineCache.AddIndexer(machineBySecretNameIndex, func(obj *v1.MachineInventory) ([]string, error) {
if obj.Spec.MachineTokenSecretName == "" {
return nil, nil
}
return []string{obj.Namespace + "/" + obj.Spec.MachineTokenSecretName}, nil
})
return server
}
func (s *sharedSecretAuth) Authenticate(resp http.ResponseWriter, req *http.Request, registerNamespace string) (*v1.MachineInventory, bool, io.WriteCloser, error) {
token := strings.TrimPrefix(req.Header.Get("Authorization"), "Bearer ")
if token == "" || registerNamespace != "" {
return nil, true, nil, nil
}
secrets, err := s.secretCache.GetByIndex(tokenIndex, token)
if apierrors.IsNotFound(err) || len(secrets) == 0 {
return nil, false, nil, fmt.Errorf("token not found")
} else if err != nil {
return nil, false, nil, err
} else if len(secrets) > 1 {
logrus.Errorf("Multiple machine secrets with the same value [%s/%s, %s/%s, ...]",
secrets[0].Namespace, secrets[0].Name, secrets[1].Namespace, secrets[1].Name)
return nil, false, nil, fmt.Errorf("token not found")
}
machines, err := s.machineCache.GetByIndex(machineBySecretNameIndex, secrets[0].Namespace+"/"+secrets[0].Name)
if len(machines) > 1 {
logrus.Errorf("Multiple machine inventories with the token: %v", machines)
}
if apierrors.IsNotFound(err) || len(machines) != 1 {
return nil, false, nil, fmt.Errorf("machine not found")
} else if err != nil {
return nil, false, nil, err
}
return machines[0], false, writeCloser{resp}, nil
}
type writeCloser struct {
io.Writer
}
func (writeCloser) Close() error {
return nil
}

View File

@@ -5,16 +5,18 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/pkg/errors"
v1 "github.com/rancher/os2/pkg/apis/rancheros.cattle.io/v1"
"github.com/rancher/os2/pkg/clients"
ranchercontrollers "github.com/rancher/os2/pkg/generated/controllers/management.cattle.io/v3"
roscontrollers "github.com/rancher/os2/pkg/generated/controllers/rancheros.cattle.io/v1"
"github.com/rancher/os2/pkg/tpm"
v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
@@ -24,43 +26,40 @@ var (
tokenKey = "token"
tokenIndex = "tokenIndex"
machineBySecretNameIndex = "machineBySecretNameIndex"
registrationTokenIndex = "registrationTokenIndex"
tpmHashIndex = "tpmHashIndex"
)
type authenticator interface {
Authenticate(resp http.ResponseWriter, req *http.Request, registerNamespace string) (*v1.MachineInventory, bool, io.WriteCloser, error)
}
type InventoryServer struct {
secretCache corecontrollers.SecretCache
settingCache ranchercontrollers.SettingCache
secretCache corecontrollers.SecretCache
machineCache roscontrollers.MachineInventoryCache
machineClient roscontrollers.MachineInventoryClient
machineRegistrationCache roscontrollers.MachineRegistrationCache
clusterRegistrationToken ranchercontrollers.ClusterRegistrationTokenCache
authenticators []authenticator
}
func New(clients *clients.Clients) *InventoryServer {
server := &InventoryServer{
authenticators: []authenticator{
tpm.New(clients),
newSharedSecretAuth(clients),
},
secretCache: clients.Core.Secret().Cache(),
settingCache: clients.Rancher.Setting().Cache(),
machineCache: clients.OS.MachineInventory().Cache(),
machineClient: clients.OS.MachineInventory(),
machineRegistrationCache: clients.OS.MachineRegistration().Cache(),
settingCache: clients.Rancher.Setting().Cache(),
clusterRegistrationToken: clients.Rancher.ClusterRegistrationToken().Cache(),
}
server.secretCache.AddIndexer(tokenIndex, func(obj *corev1.Secret) ([]string, error) {
if string(obj.Type) != tokenType {
return nil, nil
}
t := obj.Data[tokenKey]
if len(t) == 0 {
return nil, nil
}
return []string{base64.StdEncoding.EncodeToString(t)}, nil
})
server.machineCache.AddIndexer(machineBySecretNameIndex, func(obj *v1.MachineInventory) ([]string, error) {
if obj.Spec.MachineTokenSecretName == "" {
return nil, nil
}
return []string{obj.Namespace + "/" + obj.Spec.MachineTokenSecretName}, nil
})
server.secretCache.AddIndexer(tokenHash, func(obj *corev1.Secret) ([]string, error) {
if string(obj.Type) == tokenType {
if string(obj.Type) != tokenType {
return nil, nil
}
if token := obj.Data[tokenKey]; len(token) > 0 {
@@ -70,54 +69,89 @@ func New(clients *clients.Clients) *InventoryServer {
return nil, nil
})
server.machineCache.AddIndexer(tokenHash, func(obj *v1.MachineInventory) ([]string, error) {
if obj.Spec.TPMHash == "" {
return nil, nil
}
hash := sha256.Sum256([]byte(obj.Spec.TPMHash))
return []string{base64.StdEncoding.EncodeToString(hash[:])}, nil
})
server.machineRegistrationCache.AddIndexer(registrationTokenIndex, func(obj *v1.MachineRegistration) ([]string, error) {
if obj.Status.RegistrationToken == "" {
return nil, nil
}
return []string{
obj.Status.RegistrationToken,
}, nil
})
server.machineCache.AddIndexer(tpmHashIndex, func(obj *v1.MachineInventory) ([]string, error) {
if obj.Spec.TPMHash == "" {
return nil, nil
}
return []string{obj.Spec.TPMHash}, nil
})
return server
}
func (i *InventoryServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
if strings.HasSuffix(req.URL.Path, "/cacerts") {
if strings.Contains(req.URL.Path, "/registration/") {
i.register(resp, req)
} else if strings.HasSuffix(req.URL.Path, "/cacerts") {
i.cacerts(resp, req)
} else {
err := i.handle(resp, req)
if err != nil {
http.Error(resp, err.Error(), http.StatusInternalServerError)
}
i.handle(resp, req)
}
}
func (i *InventoryServer) handle(resp http.ResponseWriter, req *http.Request) error {
token := strings.TrimPrefix(req.Header.Get("Authorization"), "Bearer ")
secrets, err := i.secretCache.GetByIndex(tokenIndex, token)
if apierrors.IsNotFound(err) {
http.Error(resp, "Token not found", http.StatusNotFound)
return nil
} else if err != nil {
return err
} else if len(secrets) > 1 {
logrus.Errorf("Multiple machine secrets with the same value [%s/%s, %s/%s, ...]",
secrets[0].Namespace, secrets[0].Name, secrets[1].Namespace, secrets[1].Name)
http.Error(resp, "Token not found", http.StatusNotFound)
return nil
func (i *InventoryServer) authMachine(resp http.ResponseWriter, req *http.Request, registerNamespace string) (*v1.MachineInventory, io.WriteCloser, error) {
for _, auth := range i.authenticators {
machine, cont, writer, err := auth.Authenticate(resp, req, registerNamespace)
if err != nil {
return nil, nil, err
}
if machine != nil || !cont {
return machine, writer, nil
}
}
return nil, nil, nil
}
machines, err := i.machineCache.GetByIndex(machineBySecretNameIndex, secrets[0].Namespace+"/"+secrets[0].Name)
if len(machines) > 1 {
logrus.Errorf("Multiple machine inventories with the token: %v", machines)
func writeErr(writer io.Writer, resp http.ResponseWriter, err error) {
message := "Unauthorized"
if err != nil {
message = err.Error()
}
if apierrors.IsNotFound(err) || len(machines) != 1 {
http.Error(resp, "Machine not found", http.StatusNotFound)
return nil
} else if err != nil {
return err
if writer == nil {
http.Error(resp, message, http.StatusUnauthorized)
} else {
writer.Write([]byte(message))
}
}
crt, err := i.clusterRegistrationToken.Get(machines[0].Status.ClusterRegistrationTokenNamespace,
machines[0].Status.ClusterRegistrationTokenName)
func (i *InventoryServer) handle(resp http.ResponseWriter, req *http.Request) {
machine, writer, err := i.authMachine(resp, req, "")
if machine == nil || err != nil {
writeErr(writer, resp, err)
return
}
defer writer.Close()
if machine.Spec.ClusterName == "" {
writeErr(writer, resp, errors.New("cluster not assigned"))
return
}
crt, err := i.clusterRegistrationToken.Get(machine.Status.ClusterRegistrationTokenNamespace,
machine.Status.ClusterRegistrationTokenName)
if apierrors.IsNotFound(err) || crt.Status.Token == "" {
http.Error(resp, "Cluster token not found", http.StatusNotFound)
return nil
writeErr(writer, resp, errors.New("cluster token not assigned"))
}
return writeResponse(resp, machines[0], crt)
if err := writeResponse(writer, machine, crt); err != nil {
writeErr(writer, resp, err)
}
}
type config struct {
@@ -127,11 +161,10 @@ type config struct {
InternalAddress string `json:"internalAddress,omitempty"`
Taints []string `json:"taints,omitempty"`
Labels []string `json:"labels,omitempty"`
ConfigValues map[string]string `json:"extraConfig,omitempty"`
Token string `json:"token,omitempty"`
}
func writeResponse(resp http.ResponseWriter, inventory *v1.MachineInventory, crt *v3.ClusterRegistrationToken) error {
func writeResponse(writer io.Writer, inventory *v1.MachineInventory, crt *v3.ClusterRegistrationToken) error {
config := config{
Role: inventory.Spec.Config.Role,
NodeName: inventory.Spec.Config.NodeName,
@@ -139,7 +172,6 @@ func writeResponse(resp http.ResponseWriter, inventory *v1.MachineInventory, crt
InternalAddress: inventory.Spec.Config.InternalAddress,
Taints: nil,
Labels: nil,
ConfigValues: inventory.Spec.Config.ConfigValues,
Token: crt.Status.Token,
}
for k, v := range inventory.Spec.Config.Labels {
@@ -148,6 +180,5 @@ func writeResponse(resp http.ResponseWriter, inventory *v1.MachineInventory, crt
for _, taint := range inventory.Spec.Config.Taints {
config.Labels = append(config.Labels, taint.ToString())
}
resp.Header().Set("Content-Type", "application/json")
return json.NewEncoder(resp).Encode(config)
return json.NewEncoder(writer).Encode(config)
}

214
pkg/tpm/auth_tpm.go Normal file
View File

@@ -0,0 +1,214 @@
package tpm
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-attestation/attest"
"github.com/gorilla/websocket"
v1 "github.com/rancher/os2/pkg/apis/rancheros.cattle.io/v1"
"github.com/rancher/wrangler/pkg/merr"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (a *AuthServer) verifyChain(ek *attest.EK, namespace string) error {
secret, err := a.secretCache.Get(namespace, tpmCACert)
if apierrors.IsNotFound(err) {
return nil
}
roots := x509.NewCertPool()
_ = roots.AppendCertsFromPEM(secret.Data[corev1.TLSCertKey])
opts := x509.VerifyOptions{
Roots: roots,
}
_, err = ek.Certificate.Verify(opts)
return err
}
func (a *AuthServer) generateChallenge(ek *attest.EK, attestationData *AttestationData) ([]byte, []byte, error) {
ap := attest.ActivationParameters{
TPMVersion: attest.TPMVersion20,
EK: ek.Public,
AK: *attestationData.AK,
}
secret, ec, err := ap.Generate()
if err != nil {
return nil, nil, fmt.Errorf("generating challenge: %w", err)
}
challengeBytes, err := json.Marshal(Challenge{EC: ec})
if err != nil {
return nil, nil, fmt.Errorf("marshalling challenge: %w", err)
}
return secret, challengeBytes, nil
}
func (a *AuthServer) validateChallenge(secret, resp []byte) error {
var response ChallengeResponse
if err := json.Unmarshal(resp, &response); err != nil {
return fmt.Errorf("unmarshalling challenge response: %w", err)
}
if !bytes.Equal(secret, response.Secret) {
return fmt.Errorf("invalid challenge response")
}
return nil
}
func (a *AuthServer) validHash(ek *attest.EK, registerNamespace string) (*v1.MachineInventory, error) {
hashEncoded, err := GetPubHash(ek)
if err != nil {
return nil, fmt.Errorf("tpm: could not get public key hash: %v", err)
}
if registerNamespace != "" {
if err := a.verifyChain(ek, registerNamespace); err != nil {
return nil, fmt.Errorf("verifying chain: %w", err)
}
return &v1.MachineInventory{
ObjectMeta: metav1.ObjectMeta{
Namespace: registerNamespace,
},
Spec: v1.MachineInventorySpec{
TPMHash: hashEncoded,
},
}, nil
}
machines, err := a.machineCache.GetByIndex(machineByHash, hashEncoded)
if apierrors.IsNotFound(err) || len(machines) != 1 {
if len(machines) > 1 {
logrus.Errorf("multiple machines for same hash %s found: %v", hashEncoded, machines)
}
return nil, fmt.Errorf("failed to find machine")
}
if err := a.verifyChain(ek, machines[0].Namespace); err != nil {
return nil, fmt.Errorf("verifying chain: %w", err)
}
return machines[0], nil
}
func writeRead(conn *websocket.Conn, input []byte) ([]byte, error) {
writer, err := conn.NextWriter(websocket.BinaryMessage)
if err != nil {
return nil, err
}
if _, err := writer.Write(input); err != nil {
return nil, err
}
if err := writer.Close(); err != nil {
return nil, err
}
_, reader, err := conn.NextReader()
if err != nil {
return nil, err
}
return ioutil.ReadAll(reader)
}
func upgrade(resp http.ResponseWriter, req *http.Request) (*websocket.Conn, error) {
upgrader := websocket.Upgrader{
HandshakeTimeout: 5 * time.Second,
CheckOrigin: func(r *http.Request) bool { return true },
}
conn, err := upgrader.Upgrade(resp, req, nil)
if err != nil {
return nil, err
}
_ = conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
_ = conn.SetReadDeadline(time.Now().Add(10 * time.Second))
return conn, err
}
func (a *AuthServer) getAttestationData(header string) (*attest.EK, *AttestationData, error) {
tpmBytes, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(header, "Bearer TPM"))
if err != nil {
return nil, nil, err
}
var attestationData AttestationData
if err := json.Unmarshal(tpmBytes, &attestationData); err != nil {
return nil, nil, err
}
ek, err := DecodeEK(attestationData.EK)
if err != nil {
return nil, nil, err
}
return ek, &attestationData, nil
}
func (a *AuthServer) Authenticate(resp http.ResponseWriter, req *http.Request, registerNamespace string) (*v1.MachineInventory, bool, io.WriteCloser, error) {
header := req.Header.Get("Authorization")
if !strings.HasPrefix(header, "Bearer TPM") {
return nil, true, nil, nil
}
ek, attestationData, err := a.getAttestationData(header)
if err != nil {
return nil, false, nil, err
}
machine, err := a.validHash(ek, registerNamespace)
if err != nil {
return nil, false, nil, err
}
secret, challenge, err := a.generateChallenge(ek, attestationData)
if err != nil {
return nil, false, nil, err
}
conn, err := upgrade(resp, req)
if err != nil {
return nil, false, nil, err
}
challResp, err := writeRead(conn, challenge)
if err != nil {
return nil, false, nil, err
}
if err := a.validateChallenge(secret, challResp); err != nil {
return nil, false, nil, err
}
writer, err := conn.NextWriter(websocket.BinaryMessage)
return machine, false, &responseWriter{
WriteCloser: writer,
conn: conn,
}, err
}
type responseWriter struct {
io.WriteCloser
conn *websocket.Conn
}
func (r *responseWriter) Close() error {
err := r.WriteCloser.Close()
err2 := r.conn.Close()
return merr.NewErrors(err, err2)
}

36
pkg/tpm/tpm.go Normal file
View File

@@ -0,0 +1,36 @@
package tpm
import (
v1 "github.com/rancher/os2/pkg/apis/rancheros.cattle.io/v1"
"github.com/rancher/os2/pkg/clients"
roscontrollers "github.com/rancher/os2/pkg/generated/controllers/rancheros.cattle.io/v1"
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
)
const (
machineByHash = "machineByHash"
tpmCACert = "tpm-ca"
)
type AuthServer struct {
machineCache roscontrollers.MachineInventoryCache
machineClient roscontrollers.MachineInventoryClient
secretCache corecontrollers.SecretCache
}
func New(clients *clients.Clients) *AuthServer {
a := &AuthServer{
machineCache: clients.OS.MachineInventory().Cache(),
machineClient: clients.OS.MachineInventory(),
secretCache: clients.Core.Secret().Cache(),
}
a.machineCache.AddIndexer(machineByHash, func(obj *v1.MachineInventory) ([]string, error) {
if obj.Spec.TPMHash == "" {
return nil, nil
}
return []string{obj.Spec.TPMHash}, nil
})
return a
}

94
pkg/tpm/tpm_attestor.go Normal file
View File

@@ -0,0 +1,94 @@
/*
** Copyright 2019 Bloomberg Finance L.P.
**
** 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 tpm
import (
"crypto/sha256"
"encoding/pem"
"errors"
"fmt"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-attestation/attest"
)
type AttestationData struct {
EK []byte
AK *attest.AttestationParameters
}
type Challenge struct {
EC *attest.EncryptedCredential
}
type KeyData struct {
Keys []string `json:"keys"`
}
type ChallengeResponse struct {
Secret []byte
}
func GetPubHash(ek *attest.EK) (string, error) {
data, err := pubBytes(ek)
if err != nil {
return "", err
}
pubHash := sha256.Sum256(data)
hashEncoded := fmt.Sprintf("%x", pubHash)
return hashEncoded, nil
}
func pubBytes(ek *attest.EK) ([]byte, error) {
data, err := x509.MarshalPKIXPublicKey(ek.Public)
if err != nil {
return nil, fmt.Errorf("error marshaling ec public key: %v", err)
}
return data, nil
}
func DecodeEK(pemBytes []byte) (*attest.EK, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("invalid pemBytes")
}
switch block.Type {
case "CERTIFICATE":
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("error parsing certificate: %v", err)
}
return &attest.EK{
Certificate: cert,
Public: cert.PublicKey,
}, nil
case "PUBLIC KEY":
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("error parsing ecdsa public key: %v", err)
}
return &attest.EK{
Public: pub,
}, nil
}
return nil, fmt.Errorf("invalid pem type: %s", block.Type)
}

View File

@@ -27,7 +27,7 @@ RUN cd /usr/sbin && \
rm tmp
RUN cd /usr/src && \
git clone https://github.com/rancher-sandbox/cOS-toolkit
RUN curl -Lo /usr/bin/luet https://github.com/mudler/luet/releases/download/0.18.1/luet-0.18.1-linux-$(go env GOARCH) && \
RUN curl -Lo /usr/bin/luet https://github.com/mudler/luet/releases/download/0.20.5/luet-0.20.5-linux-$(go env GOARCH) && \
chmod +x /usr/bin/luet
RUN mkdir -p /iso/iso-overlay/boot/grub2 /etc/luet
RUN export SUFFIX; \
@@ -104,7 +104,7 @@ RUN echo -e \
'fi\n'\
'menuentry "RancherOS Install" --class os --unrestricted {\n'\
' echo Loading kernel...\n'\
' $linux ($root)/boot/kernel.xz cdroot root=live:CDLABEL=COS_LIVE rd.live.dir=/ rd.live.squashimg=rootfs.squashfs console=tty1 console=ttyS0 rd.cos.disable\n'\
' $linux ($root)/boot/kernel.xz cdroot root=live:CDLABEL=COS_LIVE rd.live.dir=/ rd.live.squashimg=rootfs.squashfs console=tty1 console=ttyS0 rd.cos.disable rancheros.install.automatic=true rancheros.install.config_url=/run/initramfs/live/config\n'\
' echo Loading initrd...\n'\
' $initrd ($root)/boot/rootfs.xz\n'\
'}\n'\
@@ -115,7 +115,13 @@ RUN echo -e \
' terminal_output console\n'\
' }\n'\
'fi\n' > /iso/iso-overlay/boot/grub2/grub.cfg
RUN echo -e '#cloud-config\n'\
'rancheros:\n'\
' install:\n'\
' automatic: false\n' > /iso/iso-overlay/config
RUN luet install --no-spinner -y toolchain/luet-makeiso
ARG CONFIG
RUN if [ -n "$CONFIG" ]; then echo "$CONFIG" > /iso/iso-overlay/config; fi
WORKDIR /usr/src/cOS-toolkit/packer
FROM tools AS iso-build
@@ -179,7 +185,10 @@ EOF
iso()
{
build --target iso -o build/
if [ -n "$CONFIG" ]; then
CONFIG_DATA="$(<$CONFIG)"
fi
build --target iso -o build/ --build-arg CONFIG="${CONFIG_DATA}"
}
qcow()
@@ -221,14 +230,16 @@ ami()
usage()
{
echo "Usage:"
echo " $0 IMAGE OUTPUT"
echo " $0 IMAGE OUTPUT [ISO_CLOUD_CONFIG]"
echo
echo " IMAGE: a Docker image"
echo " OUTPUT: Comma seperated value of output image formats. Valid: aws,iso,qcow"
echo " ISO_CLOUD_CONFIG: An option file that will be used as the default cloud-init in an ISO"
}
IMAGE=$1
OUTPUT=$2
CONFIG=$3
VERSION=${IMAGE##*:}
NAME=${IMAGE%%:${VERSION}}
NAME=${NAME//[^a-zA-Z0-9-@.\/_]/-}
@@ -258,6 +269,8 @@ fi
;;
*)
echo Unknown format $i
echo
usage
exit 1
esac
done

3
scripts/install-chart Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
helm -n cattle-rancheros-operator-system upgrade --install --create-namespace rancheros-operator ./chart

13
scripts/qemu-in-container Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
if [ "$#" = 0 ]; then
exec bash
fi
if [ -e /dev/tpm0 ]; then
mkdir /tmp/emulated_tpm
swtpm socket --tpmstate dir=/tmp/emulated_tpm --ctrl type=unixio,path=/tmp/emulated_tpm/swtpm-sock --log level=1 --tpm2 &
exec "$@" -chardev socket,id=chrtpm,path=/tmp/emulated_tpm/swtpm-sock \
-tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0
fi

6
scripts/qemu-wrapper Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
set -x -e
BASE=$(dirname $0)/..
docker build -t ros-qemu -f ${BASE}/Dockerfile.kvm ${BASE}
exec docker run -it --rm --net=host -v $(dirname $(pwd)):$(dirname $(pwd)) -w $(pwd) --privileged ros-qemu "$@"

View File

@@ -38,7 +38,8 @@ if [ "$1" == "" ] && [ ! -e output.iso ]; then
fi
#-bios /usr/share/qemu/OVMF.fd \
qemu-system-x86_64 \
../scripts/qemu-wrapper qemu-system-x86_64 \
$BOOT \
-enable-kvm \
-m ${MEMORY:=4096} \