Skip to content

Commit 0d45d1a

Browse files
committed
tests
1 parent e374c42 commit 0d45d1a

File tree

4 files changed

+167
-12
lines changed

4 files changed

+167
-12
lines changed

coderd/database/dbauthz/dbauthz.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ var (
260260
rbac.ResourceOrganization.Type: {policy.ActionCreate, policy.ActionRead},
261261
rbac.ResourceOrganizationMember.Type: {policy.ActionCreate},
262262
rbac.ResourceProvisionerDaemon.Type: {policy.ActionCreate, policy.ActionUpdate},
263+
rbac.ResourceProvisionerKeys.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
263264
rbac.ResourceUser.Type: rbac.ResourceUser.AvailableActions(),
264265
rbac.ResourceWorkspaceDormant.Type: {policy.ActionUpdate, policy.ActionDelete, policy.ActionWorkspaceStop},
265266
rbac.ResourceWorkspace.Type: {policy.ActionUpdate, policy.ActionDelete, policy.ActionWorkspaceStart, policy.ActionWorkspaceStop, policy.ActionSSH},

coderd/httpmw/provisionerdaemon.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ func ExtractProvisionerDaemonAuthenticated(opts ExtractProvisionerAuthConfig) fu
4040
}
4141

4242
if !opts.MultiOrgEnabled {
43+
if opts.PSK == "" {
44+
handleOptional(http.StatusUnauthorized, codersdk.Response{
45+
Message: "External provisioner daemons not enabled",
46+
})
47+
return
48+
}
49+
4350
fallbackToPSK(ctx, opts.PSK, next, w, r, handleOptional)
4451
return
4552
}
@@ -65,7 +72,8 @@ func ExtractProvisionerDaemonAuthenticated(opts ExtractProvisionerAuthConfig) fu
6572
return
6673
}
6774

68-
pk, err := opts.DB.GetProvisionerKeyByID(ctx, id)
75+
// nolint:gocritic // System must check if the provisioner key is valid.
76+
pk, err := opts.DB.GetProvisionerKeyByID(dbauthz.AsSystemRestricted(ctx), id)
6977
if err != nil {
7078
if httpapi.Is404Error(err) {
7179
handleOptional(http.StatusUnauthorized, codersdk.Response{
@@ -99,13 +107,6 @@ func ExtractProvisionerDaemonAuthenticated(opts ExtractProvisionerAuthConfig) fu
99107
}
100108

101109
func fallbackToPSK(ctx context.Context, psk string, next http.Handler, w http.ResponseWriter, r *http.Request, handleOptional func(code int, response codersdk.Response)) {
102-
if psk == "" {
103-
handleOptional(http.StatusUnauthorized, codersdk.Response{
104-
Message: "External provisioner daemons not enabled",
105-
})
106-
return
107-
}
108-
109110
token := r.Header.Get(codersdk.ProvisionerDaemonPSK)
110111
if subtle.ConstantTimeCompare([]byte(token), []byte(psk)) != 1 {
111112
handleOptional(http.StatusUnauthorized, codersdk.Response{

codersdk/provisionerdaemons.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,18 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione
225225
headers := http.Header{}
226226

227227
headers.Set(BuildVersionHeader, buildinfo.Version())
228-
// nolint:gocritic // Need to support multiple exclusive auth flows.
228+
229+
if req.ProvisionerKey != "" && req.PreSharedKey != "" {
230+
return nil, xerrors.Errorf("cannot provide both a provisioner key and a pre-shared key")
231+
}
229232
if req.ProvisionerKey != "" {
230233
headers.Set(ProvisionerDaemonKey, req.ProvisionerKey)
231-
} else if req.PreSharedKey != "" {
234+
}
235+
if req.PreSharedKey != "" {
232236
headers.Set(ProvisionerDaemonPSK, req.PreSharedKey)
233-
} else {
234-
// use session token if we don't have a PSK.
237+
}
238+
if req.ProvisionerKey == "" && req.PreSharedKey == "" {
239+
// use session token if we don't have a PSK or provisioner key.
235240
jar, err := cookiejar.New(nil)
236241
if err != nil {
237242
return nil, xerrors.Errorf("create cookie jar: %w", err)

enterprise/coderd/provisionerdaemons_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"github.com/coder/coder/v2/buildinfo"
1919
"github.com/coder/coder/v2/coderd/coderdtest"
2020
"github.com/coder/coder/v2/coderd/database"
21+
"github.com/coder/coder/v2/coderd/database/dbauthz"
22+
"github.com/coder/coder/v2/coderd/provisionerkey"
2123
"github.com/coder/coder/v2/coderd/rbac"
2224
"github.com/coder/coder/v2/coderd/util/ptr"
2325
"github.com/coder/coder/v2/codersdk"
@@ -552,6 +554,152 @@ func TestProvisionerDaemonServe(t *testing.T) {
552554
require.NoError(t, err)
553555
require.Len(t, daemons, 0)
554556
})
557+
558+
t.Run("ProvisionerKeyAuth", func(t *testing.T) {
559+
t.Parallel()
560+
561+
insertParams, token, err := provisionerkey.New(uuid.Nil, "dont-TEST-me")
562+
require.NoError(t, err)
563+
564+
tcs := []struct {
565+
name string
566+
psk string
567+
multiOrgFeatureEnabled bool
568+
multiOrgExperimentEnabled bool
569+
insertParams database.InsertProvisionerKeyParams
570+
requestProvisionerKey string
571+
requestPSK string
572+
errStatusCode int
573+
}{
574+
{
575+
name: "MultiOrgDisabledPSKAuthOK",
576+
psk: "provisionersftw",
577+
requestPSK: "provisionersftw",
578+
},
579+
{
580+
name: "MultiOrgExperimentDisabledPSKAuthOK",
581+
multiOrgFeatureEnabled: true,
582+
psk: "provisionersftw",
583+
requestPSK: "provisionersftw",
584+
},
585+
{
586+
name: "MultiOrgFeatureDisabledPSKAuthOK",
587+
multiOrgExperimentEnabled: true,
588+
psk: "provisionersftw",
589+
requestPSK: "provisionersftw",
590+
},
591+
{
592+
name: "MultiOrgEnabledPSKAuthOK",
593+
psk: "provisionersftw",
594+
multiOrgFeatureEnabled: true,
595+
multiOrgExperimentEnabled: true,
596+
requestPSK: "provisionersftw",
597+
},
598+
{
599+
name: "MultiOrgEnabledKeyAuthOK",
600+
psk: "provisionersftw",
601+
multiOrgFeatureEnabled: true,
602+
multiOrgExperimentEnabled: true,
603+
insertParams: insertParams,
604+
requestProvisionerKey: token,
605+
},
606+
{
607+
name: "MultiOrgEnabledPSKAuthDisabled",
608+
multiOrgFeatureEnabled: true,
609+
multiOrgExperimentEnabled: true,
610+
requestPSK: "provisionersftw",
611+
errStatusCode: http.StatusUnauthorized,
612+
},
613+
{
614+
name: "WrongKey",
615+
multiOrgFeatureEnabled: true,
616+
multiOrgExperimentEnabled: true,
617+
requestProvisionerKey: "provisionersftw",
618+
errStatusCode: http.StatusUnauthorized,
619+
},
620+
{
621+
name: "IdOKKeyWrong",
622+
multiOrgFeatureEnabled: true,
623+
multiOrgExperimentEnabled: true,
624+
requestProvisionerKey: insertParams.ID.String() + ":" + "wrong",
625+
errStatusCode: http.StatusUnauthorized,
626+
},
627+
{
628+
name: "IdWrongKeyOK",
629+
multiOrgFeatureEnabled: true,
630+
multiOrgExperimentEnabled: true,
631+
requestProvisionerKey: uuid.NewString() + ":" + token,
632+
errStatusCode: http.StatusUnauthorized,
633+
},
634+
{
635+
name: "TokenOnly",
636+
multiOrgFeatureEnabled: true,
637+
multiOrgExperimentEnabled: true,
638+
requestProvisionerKey: token,
639+
errStatusCode: http.StatusUnauthorized,
640+
},
641+
}
642+
643+
for _, tc := range tcs {
644+
t.Run(tc.name, func(t *testing.T) {
645+
t.Parallel()
646+
features := license.Features{
647+
codersdk.FeatureExternalProvisionerDaemons: 1,
648+
}
649+
if tc.multiOrgFeatureEnabled {
650+
features[codersdk.FeatureMultipleOrganizations] = 1
651+
}
652+
dv := coderdtest.DeploymentValues(t)
653+
if tc.multiOrgExperimentEnabled {
654+
dv.Experiments.Append(string(codersdk.ExperimentMultiOrganization))
655+
}
656+
client, db, user := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
657+
LicenseOptions: &coderdenttest.LicenseOptions{
658+
Features: features,
659+
},
660+
ProvisionerDaemonPSK: tc.psk,
661+
Options: &coderdtest.Options{
662+
DeploymentValues: dv,
663+
},
664+
})
665+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
666+
defer cancel()
667+
668+
if tc.insertParams.Name != "" {
669+
tc.insertParams.OrganizationID = user.OrganizationID
670+
// nolint:gocritic // test
671+
_, err := db.InsertProvisionerKey(dbauthz.AsSystemRestricted(ctx), tc.insertParams)
672+
require.NoError(t, err)
673+
}
674+
675+
another := codersdk.New(client.URL)
676+
srv, err := another.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{
677+
ID: uuid.New(),
678+
Name: testutil.MustRandString(t, 63),
679+
Organization: user.OrganizationID,
680+
Provisioners: []codersdk.ProvisionerType{
681+
codersdk.ProvisionerTypeEcho,
682+
},
683+
Tags: map[string]string{
684+
provisionersdk.TagScope: provisionersdk.ScopeOrganization,
685+
},
686+
PreSharedKey: tc.requestPSK,
687+
ProvisionerKey: tc.requestProvisionerKey,
688+
})
689+
if tc.errStatusCode != 0 {
690+
require.Error(t, err)
691+
var apiError *codersdk.Error
692+
require.ErrorAs(t, err, &apiError)
693+
require.Equal(t, http.StatusUnauthorized, apiError.StatusCode())
694+
return
695+
}
696+
697+
require.NoError(t, err)
698+
err = srv.DRPCConn().Close()
699+
require.NoError(t, err)
700+
})
701+
}
702+
})
555703
}
556704

557705
func TestGetProvisionerDaemons(t *testing.T) {

0 commit comments

Comments
 (0)