grafana/pkg/tests/apis/iam/service_account_integration...

275 lines
9.9 KiB
Go

package identity
import (
"context"
"fmt"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tests/apis"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/util/testutil"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var gvrServiceAccounts = schema.GroupVersionResource{
Group: "iam.grafana.app",
Version: "v0alpha1",
Resource: "serviceaccounts",
}
func TestIntegrationServiceAccounts(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
// TODO: Figure out why rest.Mode4 is failing
modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3}
for _, mode := range modes {
t.Run(fmt.Sprintf("Service Account CRUD operations with dual writer mode %d", mode), func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: false,
DisableAnonymous: true,
APIServerStorageType: "unified",
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"serviceaccounts.iam.grafana.app": {
DualWriterMode: mode,
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
featuremgmt.FlagKubernetesAuthnMutation,
},
})
doServiceAccountCRUDTestsUsingTheNewAPIs(t, helper)
if mode < 3 {
doServiceAccountCRUDTestsUsingTheLegacyAPIs(t, helper)
}
})
}
}
func doServiceAccountCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHelper) {
t.Run("should create service account and get it using the new APIs as a GrafanaAdmin", func(t *testing.T) {
ctx := context.Background()
saClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
GVR: gvrServiceAccounts,
})
created, err := saClient.Resource.Create(ctx, helper.LoadYAMLOrJSONFile("testdata/serviceaccount-test-create-v0.yaml"), metav1.CreateOptions{})
require.NoError(t, err)
require.NotNil(t, created)
createdSpec := created.Object["spec"].(map[string]interface{})
require.Equal(t, "Test Service Account 1", createdSpec["title"])
require.Equal(t, false, createdSpec["disabled"])
require.Empty(t, createdSpec["plugin"])
createdUID := created.GetName()
require.NotEmpty(t, createdUID)
_, err = saClient.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err)
fetched, err := saClient.Resource.Get(ctx, createdUID, metav1.GetOptions{})
require.NoError(t, err)
require.NotNil(t, fetched)
fetchedSpec := fetched.Object["spec"].(map[string]interface{})
require.Equal(t, "Test Service Account 1", fetchedSpec["title"])
require.Equal(t, false, fetchedSpec["disabled"])
require.Empty(t, fetchedSpec["plugin"])
require.Equal(t, createdUID, fetched.GetName())
require.Equal(t, "default", fetched.GetNamespace())
})
t.Run("should not be able to create service account when using a user with insufficient permissions", func(t *testing.T) {
for _, user := range []apis.User{
helper.Org1.Editor,
helper.Org1.Viewer,
} {
t.Run(fmt.Sprintf("with basic role_%s", user.Identity.GetOrgRole()), func(t *testing.T) {
ctx := context.Background()
saClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: user,
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
GVR: gvrServiceAccounts,
})
_, err := saClient.Resource.Create(ctx, helper.LoadYAMLOrJSONFile("testdata/serviceaccount-test-create-v0.yaml"), metav1.CreateOptions{})
require.Error(t, err)
var statusErr *errors.StatusError
require.ErrorAs(t, err, &statusErr)
require.Equal(t, int32(403), statusErr.ErrStatus.Code)
})
}
})
t.Run("should not be able to create service account with invalid role", func(t *testing.T) {
ctx := context.Background()
saClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
GVR: gvrServiceAccounts,
})
saToCreate := helper.LoadYAMLOrJSONFile("testdata/serviceaccount-test-invalid-role-v0.yaml")
_, err := saClient.Resource.Create(ctx, saToCreate, metav1.CreateOptions{})
require.Error(t, err)
var statusErr *errors.StatusError
require.ErrorAs(t, err, &statusErr)
require.Equal(t, int32(400), statusErr.ErrStatus.Code)
require.Contains(t, statusErr.ErrStatus.Message, "invalid role: InvalidRole")
})
t.Run("should not be able to create service account with higher role than the user", func(t *testing.T) {
ctx := context.Background()
editorWithSACreate := helper.CreateUser("custom-editor", apis.Org1, org.RoleEditor,
[]resourcepermissions.SetResourcePermissionCommand{
{Actions: []string{serviceaccounts.ActionCreate}},
})
saClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: editorWithSACreate,
Namespace: helper.Namespacer(editorWithSACreate.Identity.GetOrgID()),
GVR: gvrServiceAccounts,
})
saToCreate := helper.LoadYAMLOrJSONFile("testdata/serviceaccount-test-higher-role-v0.yaml")
_, err := saClient.Resource.Create(ctx, saToCreate, metav1.CreateOptions{})
require.Error(t, err)
var statusErr *errors.StatusError
require.ErrorAs(t, err, &statusErr)
require.Equal(t, int32(403), statusErr.ErrStatus.Code)
require.Contains(t, statusErr.ErrStatus.Message, "can not assign a role higher than user's role")
})
t.Run("should not be able to create service account without a title", func(t *testing.T) {
ctx := context.Background()
saClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
GVR: gvrServiceAccounts,
})
saToCreate := helper.LoadYAMLOrJSONFile("testdata/serviceaccount-test-no-title-v0.yaml")
_, err := saClient.Resource.Create(ctx, saToCreate, metav1.CreateOptions{})
require.Error(t, err)
var statusErr *errors.StatusError
require.ErrorAs(t, err, &statusErr)
require.Equal(t, int32(400), statusErr.ErrStatus.Code)
require.Contains(t, statusErr.ErrStatus.Message, "service account must have a title")
})
t.Run("should not be able to create external service account as a user", func(t *testing.T) {
ctx := context.Background()
saClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
GVR: gvrServiceAccounts,
})
saToCreate := helper.LoadYAMLOrJSONFile("testdata/serviceaccount-test-external-v0.yaml")
_, err := saClient.Resource.Create(ctx, saToCreate, metav1.CreateOptions{})
require.Error(t, err)
var statusErr *errors.StatusError
require.ErrorAs(t, err, &statusErr)
require.Equal(t, int32(403), statusErr.ErrStatus.Code)
require.Contains(t, statusErr.ErrStatus.Message, "only service identities can create external service accounts")
})
t.Run("should create service account with generateName and get it using the new APIs as a GrafanaAdmin", func(t *testing.T) {
ctx := context.Background()
saClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
GVR: gvrServiceAccounts,
})
created, err := saClient.Resource.Create(ctx, helper.LoadYAMLOrJSONFile("testdata/serviceaccount-test-generate-name-v0.yaml"), metav1.CreateOptions{})
require.NoError(t, err)
require.NotNil(t, created)
createdSpec := created.Object["spec"].(map[string]interface{})
require.Equal(t, "Test Service Account with GenerateName", createdSpec["title"])
require.Equal(t, false, createdSpec["disabled"])
require.Empty(t, createdSpec["plugin"])
createdUID := created.GetName()
require.NotEmpty(t, createdUID)
require.Contains(t, createdUID, "sa-")
_, err = saClient.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err)
fetched, err := saClient.Resource.Get(ctx, createdUID, metav1.GetOptions{})
require.NoError(t, err)
require.NotNil(t, fetched)
fetchedSpec := fetched.Object["spec"].(map[string]interface{})
require.Equal(t, "Test Service Account with GenerateName", fetchedSpec["title"])
require.Equal(t, false, fetchedSpec["disabled"])
require.Empty(t, fetchedSpec["plugin"])
require.Equal(t, createdUID, fetched.GetName())
require.Equal(t, "default", fetched.GetNamespace())
})
}
func doServiceAccountCRUDTestsUsingTheLegacyAPIs(t *testing.T, helper *apis.K8sTestHelper) {
t.Run("should create service account using legacy APIs and get it using the new APIs", func(t *testing.T) {
ctx := context.Background()
saClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
GVR: gvrServiceAccounts,
})
legacySAPayload := `{
"name": "Test Service Account 2",
"role": "Viewer"
}`
rsp := apis.DoRequest(helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: "POST",
Path: "/api/serviceaccounts",
Body: []byte(legacySAPayload),
}, &serviceaccounts.ServiceAccountDTO{})
require.NotNil(t, rsp)
require.Equal(t, 201, rsp.Response.StatusCode)
require.NotEmpty(t, rsp.Result.UID)
sa, err := saClient.Resource.Get(ctx, rsp.Result.UID, metav1.GetOptions{})
require.NoError(t, err)
require.NotNil(t, sa)
saSpec := sa.Object["spec"].(map[string]interface{})
require.Equal(t, "Test Service Account 2", saSpec["title"])
require.Equal(t, false, saSpec["disabled"])
require.Empty(t, saSpec["plugin"])
require.Equal(t, rsp.Result.UID, sa.GetName())
require.Equal(t, "default", sa.GetNamespace())
})
}