Skip to content

Commit

Permalink
[RC] Support configuring the core RC service with JWT auth (#32430)
Browse files Browse the repository at this point in the history
Co-authored-by: mellon85 <[email protected]>
  • Loading branch information
ameske and mellon85 authored Dec 23, 2024
1 parent 9648c1b commit 92348d9
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 8 deletions.
40 changes: 35 additions & 5 deletions pkg/config/remote/api/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"io"
"net/http"
"net/url"
"sync"
"time"

"google.golang.org/protobuf/proto"
Expand Down Expand Up @@ -46,11 +47,13 @@ type API interface {
Fetch(context.Context, *pbgo.LatestConfigsRequest) (*pbgo.LatestConfigsResponse, error)
FetchOrgData(context.Context) (*pbgo.OrgDataResponse, error)
FetchOrgStatus(context.Context) (*pbgo.OrgStatusResponse, error)
UpdatePARJWT(string)
}

// Auth defines the possible Authentication data to access the RC backend
type Auth struct {
APIKey string
PARJWT string
AppKey string
UseAppKey bool
}
Expand All @@ -59,18 +62,26 @@ type Auth struct {
type HTTPClient struct {
baseURL string
client *http.Client
header http.Header

headerLock sync.RWMutex
header http.Header
}

// NewHTTPClient returns a new HTTP configuration client
func NewHTTPClient(auth Auth, cfg model.Reader, baseURL *url.URL) (*HTTPClient, error) {
header := http.Header{
"Content-Type": []string{"application/x-protobuf"},
"DD-Api-Key": []string{auth.APIKey},
}
if auth.PARJWT != "" {
header["DD-PAR-JWT"] = []string{auth.PARJWT}
}
if auth.APIKey != "" {
header["DD-Api-Key"] = []string{auth.APIKey}
}
if auth.UseAppKey {
header["DD-Application-Key"] = []string{auth.AppKey}
}

transport := httputils.CreateHTTPTransport(cfg)
// Set the keep-alive timeout to 30s instead of the default 90s, so the http RC client is not closed by the backend
transport.IdleConnTimeout = 30 * time.Second
Expand Down Expand Up @@ -104,7 +115,8 @@ func (c *HTTPClient) Fetch(ctx context.Context, request *pbgo.LatestConfigsReque
if err != nil {
return nil, fmt.Errorf("failed to create org data request: %w", err)
}
req.Header = c.header

c.addHeaders(req)

resp, err := c.client.Do(req)
if err != nil {
Expand Down Expand Up @@ -150,7 +162,8 @@ func (c *HTTPClient) FetchOrgData(ctx context.Context) (*pbgo.OrgDataResponse, e
if err != nil {
return nil, fmt.Errorf("failed to create org data request: %w", err)
}
req.Header = c.header

c.addHeaders(req)

resp, err := c.client.Do(req)
if err != nil {
Expand Down Expand Up @@ -187,7 +200,8 @@ func (c *HTTPClient) FetchOrgStatus(ctx context.Context) (*pbgo.OrgStatusRespons
if err != nil {
return nil, fmt.Errorf("failed to create org data request: %w", err)
}
req.Header = c.header

c.addHeaders(req)

resp, err := c.client.Do(req)
if err != nil {
Expand Down Expand Up @@ -216,6 +230,22 @@ func (c *HTTPClient) FetchOrgStatus(ctx context.Context) (*pbgo.OrgStatusRespons
return response, err
}

// UpdatePARJWT allows for dynamic setting of a Private Action Runners JWT
// Token for authentication to the RC backend.
func (c *HTTPClient) UpdatePARJWT(jwt string) {
c.headerLock.Lock()
c.header.Set("DD-PAR-JWT", jwt)
c.headerLock.Unlock()
}

func (c *HTTPClient) addHeaders(req *http.Request) {
c.headerLock.RLock()
for k, v := range c.header {
req.Header[k] = v
}
c.headerLock.RUnlock()
}

func checkStatusCode(resp *http.Response) error {
// Specific case: authentication method is wrong
// we want to be descriptive about what can be done
Expand Down
15 changes: 14 additions & 1 deletion pkg/config/remote/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ type options struct {
site string
rcKey string
apiKey string
parJWT string
traceAgentEnv string
databaseFileName string
databaseFilePath string
Expand All @@ -236,6 +237,7 @@ type options struct {
var defaultOptions = options{
rcKey: "",
apiKey: "",
parJWT: "",
traceAgentEnv: "",
databaseFileName: "remote-config.db",
databaseFilePath: "",
Expand Down Expand Up @@ -327,6 +329,11 @@ func WithAPIKey(apiKey string) func(s *options) {
return func(s *options) { s.apiKey = apiKey }
}

// WithPARJWT sets the JWT for the private action runner
func WithPARJWT(jwt string) func(s *options) {
return func(s *options) { s.parJWT = jwt }
}

// WithClientCacheBypassLimit validates and sets the service client cache bypass limit
func WithClientCacheBypassLimit(limit int, cfgPath string) func(s *options) {
if limit < minCacheBypassLimit || limit > maxCacheBypassLimit {
Expand Down Expand Up @@ -387,7 +394,7 @@ func NewService(cfg model.Reader, rcType, baseRawURL, hostname string, tagsGette
backoffPolicy := backoff.NewExpBackoffPolicy(minBackoffFactor, baseBackoffTime,
options.maxBackoff.Seconds(), recoveryInterval, recoveryReset)

authKeys, err := getRemoteConfigAuthKeys(options.apiKey, options.rcKey)
authKeys, err := getRemoteConfigAuthKeys(options.apiKey, options.rcKey, options.parJWT)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -504,6 +511,12 @@ func (s *CoreAgentService) Start() {
}()
}

// UpdatePARJWT updates the stored JWT for Private Action Runners
// for authentication to the remote config backend.
func (s *CoreAgentService) UpdatePARJWT(jwt string) {
s.api.UpdatePARJWT(jwt)
}

func startWithAgentPollLoop(s *CoreAgentService) {
err := s.refresh()
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/remote/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ func (m *mockAPI) FetchOrgStatus(ctx context.Context) (*pbgo.OrgStatusResponse,
return args.Get(0).(*pbgo.OrgStatusResponse), args.Error(1)
}

func (m *mockAPI) UpdatePARJWT(jwt string) {
m.Called(jwt)
}

type mockUptane struct {
mock.Mock
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/config/remote/service/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,16 @@ func openCacheDB(path string, agentVersion string, apiKey string) (*bbolt.DB, er
type remoteConfigAuthKeys struct {
apiKey string

parJWT string

rcKeySet bool
rcKey *msgpgo.RemoteConfigKey
}

func (k *remoteConfigAuthKeys) apiAuth() api.Auth {
auth := api.Auth{
APIKey: k.apiKey,
PARJWT: k.parJWT,
}
if k.rcKeySet {
auth.UseAppKey = true
Expand All @@ -154,12 +157,15 @@ func (k *remoteConfigAuthKeys) apiAuth() api.Auth {
return auth
}

func getRemoteConfigAuthKeys(apiKey string, rcKey string) (remoteConfigAuthKeys, error) {
func getRemoteConfigAuthKeys(apiKey string, rcKey string, parJWT string) (remoteConfigAuthKeys, error) {
if rcKey == "" {
return remoteConfigAuthKeys{
apiKey: apiKey,
parJWT: parJWT,
}, nil
}

// Legacy auth with RC specific keys
rcKey = strings.TrimPrefix(rcKey, "DDRCM_")
encoding := base32.StdEncoding.WithPadding(base32.NoPadding)
rawKey, err := encoding.DecodeString(rcKey)
Expand All @@ -176,6 +182,7 @@ func getRemoteConfigAuthKeys(apiKey string, rcKey string) (remoteConfigAuthKeys,
}
return remoteConfigAuthKeys{
apiKey: apiKey,
parJWT: parJWT,
rcKeySet: true,
rcKey: &key,
}, nil
Expand Down
10 changes: 9 additions & 1 deletion pkg/config/remote/service/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func TestAuthKeys(t *testing.T) {
tests := []struct {
rcKey string
apiKey string
parJWT string
err bool
output remoteConfigAuthKeys
}{
Expand All @@ -42,10 +43,17 @@ func TestAuthKeys(t *testing.T) {
{rcKey: generateKey(t, 2, "datadoghq.com", ""), err: true},
{rcKey: generateKey(t, 2, "", "app_Key"), err: true},
{rcKey: generateKey(t, 0, "datadoghq.com", "app_Key"), err: true},
{parJWT: "myJWT", err: false, output: remoteConfigAuthKeys{
parJWT: "myJWT",
}},
{parJWT: "myJWT", apiKey: "myAPIKey", err: false, output: remoteConfigAuthKeys{
parJWT: "myJWT",
apiKey: "myAPIKey",
}},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s|%s", test.apiKey, test.rcKey), func(tt *testing.T) {
output, err := getRemoteConfigAuthKeys(test.apiKey, test.rcKey)
output, err := getRemoteConfigAuthKeys(test.apiKey, test.rcKey, test.parJWT)
if test.err {
assert.Error(tt, err)
} else {
Expand Down

0 comments on commit 92348d9

Please sign in to comment.