From 79d49e1e86048680ab2ac2df5575f9d4460767b3 Mon Sep 17 00:00:00 2001 From: David Allen <16520934+davidallendj@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:02:49 -0700 Subject: [PATCH 1/2] refactor: change meta-data structure to store map instead of list (#33) --- internal/memstore/ciMemStore.go | 4 ++-- internal/memstore/ciMemStore_test.go | 32 ++++++++++++++-------------- pkg/citypes/models.go | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/internal/memstore/ciMemStore.go b/internal/memstore/ciMemStore.go index 853e36c..fcbd3b8 100644 --- a/internal/memstore/ciMemStore.go +++ b/internal/memstore/ciMemStore.go @@ -85,7 +85,7 @@ func (m *MemStore) Get(id string, groupLabels []string) (citypes.CI, error) { Name: id, CIData: citypes.CIData{ UserData: map[string]any{}, - MetaData: map[string]any{"groups": make(map[string][]citypes.MetaDataKV)}, // groups is a map of group name to list of key/value pairs + MetaData: map[string]any{"groups": make(map[string]citypes.MetaDataKV)}, // groups is a map of group name to list of key/value pairs }, } @@ -106,7 +106,7 @@ func (m *MemStore) Get(id string, groupLabels []string) (citypes.CI, error) { log.Debug().Msgf("groupData.Name: %v", groupData.Name) log.Debug().Msgf("groupData.Data: %v", groupData.Data) log.Debug().Msgf("groupData.Actions: %v", groupData.Actions) - groups := ci.CIData.MetaData["groups"].(map[string][]citypes.MetaDataKV) + groups := ci.CIData.MetaData["groups"].(map[string]citypes.MetaDataKV) groups[groupLabel] = groupData.Data log.Debug().Msgf("Adding groups to MetaData: %v", groups) ci.CIData.MetaData["groups"] = groups diff --git a/internal/memstore/ciMemStore_test.go b/internal/memstore/ciMemStore_test.go index a8c24c8..7af1271 100644 --- a/internal/memstore/ciMemStore_test.go +++ b/internal/memstore/ciMemStore_test.go @@ -19,8 +19,8 @@ func TestMemStore_Get(t *testing.T) { t.Run("Group exists in store", func(t *testing.T) { group1Data := citypes.GroupData{ Name: "group1", - Data: []citypes.MetaDataKV{ - {"key": "value"}, + Data: citypes.MetaDataKV{ + "key": "value", }, } store.groups = make(map[string]citypes.GroupData) @@ -42,10 +42,10 @@ func TestMemStore_Get(t *testing.T) { t.Run("Multiple groups exist in store", func(t *testing.T) { store.groups["computes"] = citypes.GroupData{ - Data: []citypes.MetaDataKV{ - {"os_version": "rocky9"}, - {"cluster_name": "hill"}, - {"admin": "groves"}, + Data: citypes.MetaDataKV{ + "os_version": "rocky9", + "cluster_name": "hill", + "admin": "groves", }, Actions: map[string]any{ "write_files": []citypes.WriteFiles{ @@ -53,9 +53,9 @@ func TestMemStore_Get(t *testing.T) { }}, } store.groups["row1"] = citypes.GroupData{ - Data: []citypes.MetaDataKV{ - {"rack": "rack1"}, - {"syslog_aggregator": "syslog1"}, + Data: citypes.MetaDataKV{ + "rack": "rack1", + "syslog_aggregator": "syslog1", }, Actions: map[string]any{ "write_files": []citypes.WriteFiles{ @@ -84,10 +84,10 @@ func TestMemStoreAddGroupData(t *testing.T) { // Test case: Add group data to cloud-init data err := store.AddGroupData("computes", citypes.GroupData{ - Data: []citypes.MetaDataKV{ - {"os_version": "rocky9"}, - {"cluster_name": "hill"}, - {"admin": "groves"}, + Data: citypes.MetaDataKV{ + "os_version": "rocky9", + "cluster_name": "hill", + "admin": "groves", }, Actions: map[string]any{ "write_files": []citypes.WriteFiles{ @@ -96,9 +96,9 @@ func TestMemStoreAddGroupData(t *testing.T) { }) assert.NoError(t, err) store.AddGroupData("row1", citypes.GroupData{ - Data: []citypes.MetaDataKV{ - {"rack": "rack1"}, - {"syslog_aggregator": "syslog1"}, + Data: citypes.MetaDataKV{ + "rack": "rack1", + "syslog_aggregator": "syslog1", }, Actions: map[string]any{ "write_files": []citypes.WriteFiles{ diff --git a/pkg/citypes/models.go b/pkg/citypes/models.go index f2c180b..f96612c 100644 --- a/pkg/citypes/models.go +++ b/pkg/citypes/models.go @@ -24,7 +24,7 @@ type WriteFiles struct { type GroupData struct { Name string `json:"name,omitempty"` - Data []MetaDataKV `json:"meta-data,omitempty"` + Data MetaDataKV `json:"meta-data,omitempty"` Actions map[string]any `json:"user-data,omitempty"` } From e7d9f93f6095f9d24c8a5ac92ad88cad3717f012 Mon Sep 17 00:00:00 2001 From: David Allen <16520934+davidallendj@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:04:55 -0700 Subject: [PATCH 2/2] Add `--access-token` and `--cacert` flags (#32) * cmd: added --access-token flag * cmd: added --cacert flag and client code --- cmd/cloud-init-server/main.go | 6 ++++- internal/smdclient/SMDclient.go | 41 ++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/cmd/cloud-init-server/main.go b/cmd/cloud-init-server/main.go index d1e0b95..5c4d642 100644 --- a/cmd/cloud-init-server/main.go +++ b/cmd/cloud-init-server/main.go @@ -26,6 +26,8 @@ var ( smdEndpoint = "http://smd:27779" jwksUrl = "" // jwt keyserver URL for secure-route token validation insecure = false + accessToken = "" + certPath = "" store ciStore ) @@ -34,6 +36,8 @@ func main() { flag.StringVar(&tokenEndpoint, "token-url", tokenEndpoint, "OIDC server URL (endpoint) to fetch new tokens from (for SMD access)") flag.StringVar(&smdEndpoint, "smd-url", smdEndpoint, "http IP/url and port for running SMD") flag.StringVar(&jwksUrl, "jwks-url", jwksUrl, "JWT keyserver URL, required to enable secure route") + flag.StringVar(&accessToken, "access-token", accessToken, "encoded JWT access token") + flag.StringVar(&certPath, "cacert", certPath, "Path to CA cert. (defaults to system CAs)") flag.BoolVar(&insecure, "insecure", insecure, "Set to bypass TLS verification for requests") flag.Parse() @@ -78,7 +82,7 @@ func main() { fakeSm.Summary() sm = fakeSm } else { - sm = smdclient.NewSMDClient(smdEndpoint, tokenEndpoint, insecure) + sm = smdclient.NewSMDClient(smdEndpoint, tokenEndpoint, accessToken, certPath, insecure) } // Unsecured datastore and router diff --git a/internal/smdclient/SMDclient.go b/internal/smdclient/SMDclient.go index 4d19c4e..c7a48e5 100644 --- a/internal/smdclient/SMDclient.go +++ b/internal/smdclient/SMDclient.go @@ -2,10 +2,11 @@ package smdclient import ( "crypto/tls" + "crypto/x509" "encoding/json" "errors" "io" - "log" + "net" "net/http" "os" "strings" @@ -13,6 +14,7 @@ import ( base "github.com/Cray-HPE/hms-base" "github.com/OpenCHAMI/smd/v2/pkg/sm" + "github.com/rs/zerolog/log" ) // Create an SMDClient Interface which can be more easily tested and mocked @@ -42,7 +44,7 @@ type SMDClient struct { // NewSMDClient creates a new SMDClient which connects to the SMD server at baseurl // and uses the provided JWT server for authentication -func NewSMDClient(baseurl string, jwtURL string, insecure bool) *SMDClient { +func NewSMDClient(baseurl string, jwtURL string, accessToken string, certPath string, insecure bool) *SMDClient { c := &http.Client{Timeout: 2 * time.Second} if insecure { c.Transport = &http.Transport{ @@ -50,12 +52,39 @@ func NewSMDClient(baseurl string, jwtURL string, insecure bool) *SMDClient { InsecureSkipVerify: true, }, } + } else { + cacert, err := os.ReadFile(certPath) + if err != nil { + log.Error().Err(err).Msgf("failed to read cert from path %s", certPath) + return nil + } + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(cacert) + + // add cert pool to client if valid + if certPool != nil { + // make sure that we can access the internal client + c.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: certPool, + InsecureSkipVerify: true, + }, + DisableKeepAlives: true, + Dial: (&net.Dialer{ + Timeout: 120 * time.Second, + KeepAlive: 120 * time.Second, + }).Dial, + TLSHandshakeTimeout: 120 * time.Second, + ResponseHeaderTimeout: 120 * time.Second, + } + } + } return &SMDClient{ smdClient: c, smdBaseURL: baseurl, tokenEndpoint: jwtURL, - accessToken: "", + accessToken: accessToken, } } @@ -78,13 +107,13 @@ func (s *SMDClient) getSMD(ep string, smd interface{}) error { if resp.StatusCode == http.StatusUnauthorized { // Request failed; handle appropriately (based on whether or not // this was a fresh JWT) - log.Println("Cached JWT was rejected by SMD") + log.Info().Msg("Cached JWT was rejected by SMD") if !freshToken { - log.Println("Fetching new JWT and retrying...") + log.Info().Msg("Fetching new JWT and retrying...") s.RefreshToken() freshToken = true } else { - log.Fatalln("SMD authentication failed, even with a fresh" + + log.Info().Msg("SMD authentication failed, even with a fresh" + " JWT. Something has gone terribly wrong; exiting to" + " avoid invalid request spam.") os.Exit(2)