feat: added admin endpoint for approving csrs

This commit is contained in:
Zach Jones 2022-06-06 16:47:21 -06:00
Родитель 7480d45142
Коммит ff5fbc91eb
6 изменённых файлов: 319 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,62 @@
package frontend
// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
import (
"context"
"net/http"
"path/filepath"
"strings"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
"github.com/Azure/ARO-RP/pkg/frontend/middleware"
)
func (f *frontend) postAdminOpenShiftClusterApproveCSR(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry)
r.URL.Path = filepath.Dir(r.URL.Path)
err := f._postAdminOpenShiftClusterApproveCSR(ctx, r, log)
adminReply(log, w, nil, nil, err)
}
func (f *frontend) _postAdminOpenShiftClusterApproveCSR(ctx context.Context, r *http.Request, log *logrus.Entry) error {
vars := mux.Vars(r)
approveAll := strings.EqualFold(r.URL.Query().Get("approveAll"), "true")
csrName := ""
if !approveAll {
csrName = r.URL.Query().Get("csrName")
err := validateAdminKubernetesObjects(r.Method, "CertificateSigningRequest", "", csrName)
if err != nil {
return err
}
}
resourceID := strings.TrimPrefix(r.URL.Path, "/admin")
doc, err := f.dbOpenShiftClusters.Get(ctx, resourceID)
switch {
case cosmosdb.IsErrorStatusCode(err, http.StatusNotFound):
return api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "", "The Resource '%s/%s' under resource group '%s' was not found.", vars["resourceType"], vars["resourceName"], vars["resourceGroupName"])
case err != nil:
return err
}
k, err := f.kubeActionsFactory(log, f.env, doc.OpenShiftCluster)
if err != nil {
return err
}
if approveAll {
return k.RunCertificateMassApprove(ctx)
}
return k.RunCertificateApprove(ctx, csrName)
}

Просмотреть файл

@ -0,0 +1,130 @@
package frontend
// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
import (
"context"
"fmt"
"net/http"
"strings"
"testing"
"github.com/golang/mock/gomock"
"github.com/sirupsen/logrus"
"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/env"
"github.com/Azure/ARO-RP/pkg/frontend/adminactions"
"github.com/Azure/ARO-RP/pkg/metrics/noop"
mock_adminactions "github.com/Azure/ARO-RP/pkg/util/mocks/adminactions"
)
func TestAdminApproveCSR(t *testing.T) {
mockSubID := "00000000-0000-0000-0000-000000000000"
mockTenantID := "00000000-0000-0000-0000-000000000000"
ctx := context.Background()
type test struct {
name string
resourceID string
csrName string
approveAll string
mocks func(*test, *mock_adminactions.MockKubeActions)
method string
wantStatusCode int
wantResponse []byte
wantError string
}
for _, tt := range []*test{
{
method: http.MethodPost,
name: "single csr",
resourceID: fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
csrName: "aro-csr",
mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
k.EXPECT().
RunCertificateApprove(gomock.Any(), tt.csrName).
Return(nil)
},
wantStatusCode: http.StatusOK,
},
{
method: http.MethodPost,
name: "all csrs",
resourceID: fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
csrName: "aro-csr",
approveAll: "true",
mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
k.EXPECT().
RunCertificateMassApprove(gomock.Any()).
Return(nil)
},
wantStatusCode: http.StatusOK,
},
{
method: http.MethodPost,
name: "invalid csr name",
resourceID: fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
csrName: "",
approveAll: "false",
mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
},
wantStatusCode: http.StatusBadRequest,
wantError: "400: InvalidParameter: : The provided name '' is invalid.",
},
} {
t.Run(fmt.Sprintf("%s: %s", tt.method, tt.name), func(t *testing.T) {
ti := newTestInfra(t).WithOpenShiftClusters().WithSubscriptions()
defer ti.done()
k := mock_adminactions.NewMockKubeActions(ti.controller)
tt.mocks(tt, k)
ti.fixture.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{
Key: strings.ToLower(tt.resourceID),
OpenShiftCluster: &api.OpenShiftCluster{
ID: tt.resourceID,
Name: "resourceName",
Type: "Microsoft.RedHatOpenShift/openshiftClusters",
},
})
ti.fixture.AddSubscriptionDocuments(&api.SubscriptionDocument{
ID: mockSubID,
Subscription: &api.Subscription{
State: api.SubscriptionStateRegistered,
Properties: &api.SubscriptionProperties{
TenantID: mockTenantID,
},
},
})
err := ti.buildFixtures(nil)
if err != nil {
t.Fatal(err)
}
f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) {
return k, nil
}, nil, nil)
if err != nil {
t.Fatal(err)
}
go f.Run(ctx, nil, nil)
resp, b, err := ti.request(tt.method,
fmt.Sprintf("https://server/admin%s/approvecsr?csrName=%s&approveAll=%s", tt.resourceID, tt.csrName, tt.approveAll),
nil, nil)
if err != nil {
t.Fatal(err)
}
err = validateResponse(resp, b, tt.wantStatusCode, tt.wantError, tt.wantResponse)
if err != nil {
t.Error(err)
}
})
}
}

Просмотреть файл

@ -0,0 +1,74 @@
package adminactions
// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
import (
"context"
"net/http"
certificatesv1 "k8s.io/api/certificates/v1"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/Azure/ARO-RP/pkg/api"
)
func (k *kubeActions) RunCertificateApprove(ctx context.Context, csrName string) error {
csr, err := k.kubecli.CertificatesV1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
if kerrors.IsNotFound(err) {
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeResourceNotFound, "", "certificate signing request '%s' was not found.", csrName)
}
return k.RunCertificateApprovalUpdate(ctx, csr)
}
func (k *kubeActions) RunCertificateMassApprove(ctx context.Context) error {
csrs, err := k.kubecli.CertificatesV1().CertificateSigningRequests().List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
for _, csr := range csrs.Items {
err = k.RunCertificateApprovalUpdate(ctx, &csr)
if err != nil {
return err
}
}
return nil
}
func (k *kubeActions) RunCertificateApprovalUpdate(ctx context.Context, csr *certificatesv1.CertificateSigningRequest) error {
modifiedCSR, hasCondition, err := addConditionIfNeeded(csr, string(certificatesv1.CertificateDenied), string(certificatesv1.CertificateApproved), "AROSupportApprove", "This CSR was approved by ARO support personnel.")
if err != nil {
return err
}
if !hasCondition {
_, err = k.kubecli.CertificatesV1().CertificateSigningRequests().UpdateApproval(ctx, modifiedCSR.Name, modifiedCSR, metav1.UpdateOptions{})
}
return err
}
func addConditionIfNeeded(csr *certificatesv1.CertificateSigningRequest, mustNotHaveConditionType, conditionType, reason, message string) (*certificatesv1.CertificateSigningRequest, bool, error) {
var alreadyHasCondition bool
for _, c := range csr.Status.Conditions {
if string(c.Type) == mustNotHaveConditionType {
return nil, false, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodePropertyChangeNotAllowed, "", "certificate signing request %q is already %s", csr.Name, c.Type)
}
if string(c.Type) == conditionType {
alreadyHasCondition = true
}
}
if alreadyHasCondition {
return csr, true, nil
}
csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.RequestConditionType(conditionType),
Status: corev1.ConditionTrue,
Reason: reason,
Message: message,
LastUpdateTime: metav1.Now(),
})
return csr, false, nil
}

Просмотреть файл

@ -10,6 +10,7 @@ import (
"github.com/Azure/go-autorest/autorest/to"
configclient "github.com/openshift/client-go/config/clientset/versioned"
"github.com/sirupsen/logrus"
certificatesv1 "k8s.io/api/certificates/v1"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -31,6 +32,9 @@ type KubeActions interface {
KubeDelete(ctx context.Context, groupKind, namespace, name string, force bool) error
CordonNode(ctx context.Context, nodeName string, unschedulable bool) error
DrainNode(ctx context.Context, nodeName string) error
RunCertificateApprove(ctx context.Context, csrName string) error
RunCertificateMassApprove(ctx context.Context) error
RunCertificateApprovalUpdate(ctx context.Context, csr *certificatesv1.CertificateSigningRequest) error
Upgrade(ctx context.Context, upgradeY bool) error
KubeGetPodLogs(ctx context.Context, namespace, name, containerName string) ([]byte, error)
}

Просмотреть файл

@ -256,6 +256,12 @@ func (f *frontend) authenticatedRoutes(r *mux.Router) {
s.Methods(http.MethodPost).HandlerFunc(f.postAdminKubernetesObjects).Name("postAdminKubernetesObjects")
s.Methods(http.MethodDelete).HandlerFunc(f.deleteAdminKubernetesObjects).Name("deleteAdminKubernetesObjects")
s = r.
Path("/admin/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}/approvecsr").
Subrouter()
s.Methods(http.MethodPost).HandlerFunc(f.postAdminOpenShiftClusterApproveCSR).Name("postAdminOpenShiftClusterApproveCSR")
// Pod logs
s = r.
Path("/admin/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}/kubernetespodlogs").

Просмотреть файл

@ -12,6 +12,7 @@ import (
compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute"
gomock "github.com/golang/mock/gomock"
logrus "github.com/sirupsen/logrus"
v1 "k8s.io/api/certificates/v1"
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -139,6 +140,48 @@ func (mr *MockKubeActionsMockRecorder) KubeList(arg0, arg1, arg2 interface{}) *g
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KubeList", reflect.TypeOf((*MockKubeActions)(nil).KubeList), arg0, arg1, arg2)
}
// RunCertificateApprovalUpdate mocks base method.
func (m *MockKubeActions) RunCertificateApprovalUpdate(arg0 context.Context, arg1 *v1.CertificateSigningRequest) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RunCertificateApprovalUpdate", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// RunCertificateApprovalUpdate indicates an expected call of RunCertificateApprovalUpdate.
func (mr *MockKubeActionsMockRecorder) RunCertificateApprovalUpdate(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunCertificateApprovalUpdate", reflect.TypeOf((*MockKubeActions)(nil).RunCertificateApprovalUpdate), arg0, arg1)
}
// RunCertificateApprove mocks base method.
func (m *MockKubeActions) RunCertificateApprove(arg0 context.Context, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RunCertificateApprove", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// RunCertificateApprove indicates an expected call of RunCertificateApprove.
func (mr *MockKubeActionsMockRecorder) RunCertificateApprove(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunCertificateApprove", reflect.TypeOf((*MockKubeActions)(nil).RunCertificateApprove), arg0, arg1)
}
// RunCertificateMassApprove mocks base method.
func (m *MockKubeActions) RunCertificateMassApprove(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RunCertificateMassApprove", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// RunCertificateMassApprove indicates an expected call of RunCertificateMassApprove.
func (mr *MockKubeActionsMockRecorder) RunCertificateMassApprove(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunCertificateMassApprove", reflect.TypeOf((*MockKubeActions)(nil).RunCertificateMassApprove), arg0)
}
// Upgrade mocks base method.
func (m *MockKubeActions) Upgrade(arg0 context.Context, arg1 bool) error {
m.ctrl.T.Helper()