diff --git a/models/kms_latency_histogram.go b/models/kms_latency_histogram.go index 07b6dc21d..ea90d2dd6 100644 --- a/models/kms_latency_histogram.go +++ b/models/kms_latency_histogram.go @@ -36,6 +36,9 @@ type KmsLatencyHistogram struct { // duration Duration int64 `json:"duration,omitempty"` + + // total + Total int64 `json:"total,omitempty"` } // Validate validates this kms latency histogram diff --git a/models/kms_metrics_response.go b/models/kms_metrics_response.go index 67da3ad53..e73fe2035 100644 --- a/models/kms_metrics_response.go +++ b/models/kms_metrics_response.go @@ -24,6 +24,7 @@ package models import ( "context" + "strconv" "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" @@ -57,7 +58,7 @@ type KmsMetricsResponse struct { // latency histogram // Required: true - LatencyHistogram *KmsLatencyHistogram `json:"latencyHistogram"` + LatencyHistogram []*KmsLatencyHistogram `json:"latencyHistogram"` // request active // Required: true @@ -196,15 +197,22 @@ func (m *KmsMetricsResponse) validateLatencyHistogram(formats strfmt.Registry) e return err } - if m.LatencyHistogram != nil { - if err := m.LatencyHistogram.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("latencyHistogram") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("latencyHistogram") - } - return err + for i := 0; i < len(m.LatencyHistogram); i++ { + if swag.IsZero(m.LatencyHistogram[i]) { // not required + continue } + + if m.LatencyHistogram[i] != nil { + if err := m.LatencyHistogram[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("latencyHistogram" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("latencyHistogram" + "." + strconv.Itoa(i)) + } + return err + } + } + } return nil @@ -298,15 +306,19 @@ func (m *KmsMetricsResponse) ContextValidate(ctx context.Context, formats strfmt func (m *KmsMetricsResponse) contextValidateLatencyHistogram(ctx context.Context, formats strfmt.Registry) error { - if m.LatencyHistogram != nil { - if err := m.LatencyHistogram.ContextValidate(ctx, formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("latencyHistogram") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("latencyHistogram") + for i := 0; i < len(m.LatencyHistogram); i++ { + + if m.LatencyHistogram[i] != nil { + if err := m.LatencyHistogram[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("latencyHistogram" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("latencyHistogram" + "." + strconv.Itoa(i)) + } + return err } - return err } + } return nil diff --git a/portal-ui/src/common/SecureComponent/permissions.ts b/portal-ui/src/common/SecureComponent/permissions.ts index 9d05c9d30..e0c179c72 100644 --- a/portal-ui/src/common/SecureComponent/permissions.ts +++ b/portal-ui/src/common/SecureComponent/permissions.ts @@ -113,6 +113,26 @@ export const IAM_SCOPES = { ADMIN_INSPECT_DATA: "admin:InspectData", S3_ALL_ACTIONS: "s3:*", ADMIN_ALL_ACTIONS: "admin:*", + KMS_ALL_ACTIONS: "kms:*", + KMS_STATUS: "kms:Status", + KMS_METRICS: "kms:Metrics", + KMS_APIS: "kms:API", + KMS_Version: "kms:Version", + KMS_CREATE_KEY: "kms:CreateKey", + KMS_DELETE_KEY: "kms:DeleteKey", + KMS_LIST_KEYS: "kms:ListKeys", + KMS_IMPORT_KEY: "kms:ImportKey", + KMS_KEY_STATUS: "kms:KeyStatus", + KMS_DESCRIBE_POLICY: "kms:DescribePolicy", + KMS_ASSIGN_POLICY: "kms:AssignPolicy", + KMS_DELETE_POLICY: "kms:DeletePolicy", + KMS_SET_POLICY: "kms:SetPolicy", + KMS_GET_POLICY: "kms:GetPolicy", + KMS_LIST_POLICIES: "kms:ListPolicies", + KMS_DESCRIBE_IDENTITY: "kms:DescribeIdentity", + KMS_DESCRIBE_SELF_IDENTITY: "kms:DescribeSelfIdentity", + KMS_DELETE_IDENTITY: "kms:DeleteIdentity", + KMS_LIST_IDENTITIES: "kms:ListIdentities", }; export const IAM_PAGES = { @@ -160,6 +180,13 @@ export const IAM_PAGES = { /* Health */ HEALTH: "/health", + /* KMS */ + KMS: "/kms", + KMS_STATUS: "/kms/status", + KMS_KEYS: "/kms/keys", + KMS_KEYS_ADD: "/kms/add-key/", + KMS_KEYS_IMPORT: "/kms/import-key/", + /* Support */ TOOLS: "/support", REGISTER_SUPPORT: "/support/register", @@ -454,6 +481,24 @@ export const IAM_PAGES_PERMISSIONS = { IAM_SCOPES.ADMIN_SERVER_INFO, IAM_SCOPES.ADMIN_CONFIG_UPDATE, ], + [IAM_PAGES.KMS]: [IAM_SCOPES.KMS_ALL_ACTIONS], + [IAM_PAGES.KMS_STATUS]: [IAM_SCOPES.KMS_ALL_ACTIONS, IAM_SCOPES.KMS_STATUS], + [IAM_PAGES.KMS_KEYS]: [ + IAM_SCOPES.KMS_ALL_ACTIONS, + IAM_SCOPES.KMS_CREATE_KEY, + IAM_SCOPES.KMS_DELETE_KEY, + IAM_SCOPES.KMS_LIST_KEYS, + IAM_SCOPES.KMS_IMPORT_KEY, + IAM_SCOPES.KMS_KEY_STATUS, + ], + [IAM_PAGES.KMS_KEYS_ADD]: [ + IAM_SCOPES.KMS_ALL_ACTIONS, + IAM_SCOPES.KMS_CREATE_KEY, + ], + [IAM_PAGES.KMS_KEYS_IMPORT]: [ + IAM_SCOPES.KMS_ALL_ACTIONS, + IAM_SCOPES.KMS_IMPORT_KEY, + ], [IAM_PAGES.IDP_LDAP_CONFIGURATIONS]: [ IAM_SCOPES.ADMIN_ALL_ACTIONS, IAM_SCOPES.ADMIN_CONFIG_UPDATE, diff --git a/portal-ui/src/icons/SidebarMenus/EncryptionIcon.tsx b/portal-ui/src/icons/SidebarMenus/EncryptionIcon.tsx new file mode 100644 index 000000000..d51a353aa --- /dev/null +++ b/portal-ui/src/icons/SidebarMenus/EncryptionIcon.tsx @@ -0,0 +1,37 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import * as React from "react"; +import { SVGProps } from "react"; + +const EncryptionIcon = (props: SVGProps) => ( + + + +); + +export default EncryptionIcon; diff --git a/portal-ui/src/icons/SidebarMenus/EncryptionStatusIcon.tsx b/portal-ui/src/icons/SidebarMenus/EncryptionStatusIcon.tsx new file mode 100644 index 000000000..866c41f13 --- /dev/null +++ b/portal-ui/src/icons/SidebarMenus/EncryptionStatusIcon.tsx @@ -0,0 +1,38 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import * as React from "react"; +import { SVGProps } from "react"; + +const EncryptionStatusIcon = (props: SVGProps) => ( + + + +); + +export default EncryptionStatusIcon; diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/AddKeyModal.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/AddKeyModal.tsx new file mode 100644 index 000000000..e01367074 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/AddKeyModal.tsx @@ -0,0 +1,95 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import { DialogContentText, Grid } from "@mui/material"; +import React, { useState } from "react"; +import { ErrorResponseHandler } from "../../../../common/types"; +import { useAppDispatch } from "../../../../store"; +import { setErrorSnackMessage } from "../../../../systemSlice"; +import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import useApi from "../../Common/Hooks/useApi"; +import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog"; +import KMSHelpBox from "../../KMS/KMSHelpbox"; + +interface IAddKeyModalProps { + closeAddModalAndRefresh: (refresh: boolean) => void; + addOpen: boolean; +} + +const AddKeyModal = ({ + closeAddModalAndRefresh, + addOpen, +}: IAddKeyModalProps) => { + const dispatch = useAppDispatch(); + const onAddSuccess = () => closeAddModalAndRefresh(true); + const onAddError = (err: ErrorResponseHandler) => { + closeAddModalAndRefresh(false); + dispatch(setErrorSnackMessage(err)); + }; + const onClose = () => closeAddModalAndRefresh(false); + + const [addLoading, invokeAddApi] = useApi(onAddSuccess, onAddError); + const [keyName, setKeyName] = useState(""); + + const onConfirmAdd = () => { + invokeAddApi("POST", "/api/v1/kms/keys/", { key: keyName }); + }; + + return ( + + + + + ) => { + setKeyName(e.target.value); + }} + /> + + + } + /> + ); +}; + +export default AddKeyModal; diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableBucketEncryption.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableBucketEncryption.tsx index bddeb26a6..fa2dfd287 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableBucketEncryption.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableBucketEncryption.tsx @@ -14,10 +14,10 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { useEffect, useState } from "react"; +import React, { Fragment, useEffect, useState } from "react"; import Grid from "@mui/material/Grid"; import { LinearProgress, SelectChangeEvent } from "@mui/material"; -import { Button } from "mds"; +import { AddIcon, Button } from "mds"; import { Theme } from "@mui/material/styles"; import createStyles from "@mui/styles/createStyles"; import withStyles from "@mui/styles/withStyles"; @@ -29,12 +29,18 @@ import { BucketEncryptionInfo } from "../types"; import { ErrorResponseHandler } from "../../../../common/types"; import api from "../../../../common/api"; import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper"; -import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper"; import { BucketEncryptionIcon } from "mds"; import { setModalErrorSnackMessage } from "../../../../systemSlice"; import { useAppDispatch } from "../../../../store"; +import { + CONSOLE_UI_RESOURCE, + IAM_SCOPES, +} from "../../../../common/SecureComponent/permissions"; +import { SecureComponent } from "../../../../common/SecureComponent"; +import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper"; +import AddKeyModal from "./AddKeyModal"; const styles = (theme: Theme) => createStyles({ @@ -62,6 +68,9 @@ const EnableBucketEncryption = ({ const [loading, setLoading] = useState(false); const [kmsKeyID, setKmsKeyID] = useState(""); const [encryptionType, setEncryptionType] = useState("disabled"); + const [keys, setKeys] = useState<[]>([]); + const [loadingKeys, setLoadingKeys] = useState(false); + const [addOpen, setAddOpen] = useState(false); useEffect(() => { if (encryptionCfg) { @@ -74,6 +83,21 @@ const EnableBucketEncryption = ({ } }, [encryptionCfg]); + useEffect(() => { + if (encryptionType === "sse-kms") { + api + .invoke("GET", `/api/v1/kms/keys`) + .then((res: any) => { + setKeys(res.results); + setLoadingKeys(false); + }) + .catch((err: ErrorResponseHandler) => { + setLoadingKeys(false); + dispatch(setModalErrorSnackMessage(err)); + }); + } + }, [encryptionType, loadingKeys, dispatch]); + const enableBucketEncryption = (event: React.FormEvent) => { event.preventDefault(); if (loading) { @@ -108,90 +132,132 @@ const EnableBucketEncryption = ({ }; return ( - { - closeModalAndRefresh(); - }} - title="Enable Bucket Encryption" - titleIcon={} - > -
) => { - enableBucketEncryption(e); + + {addOpen && ( + { + setAddOpen(false); + setLoadingKeys(true); + }} + /> + )} + + { + closeModalAndRefresh(); }} + title="Enable Bucket Encryption" + titleIcon={} > - - - - ) => { - setEncryptionType(e.target.value as string); + ) => { + enableBucketEncryption(e); + }} + > + + + + ) => { + setEncryptionType(e.target.value as string); + }} + id="select-encryption-type" + name="select-encryption-type" + label={"Encryption Type"} + value={encryptionType} + options={[ + { + label: "Disabled", + value: "disabled", + }, + { + label: "SSE-S3", + value: "sse-s3", + }, + { + label: "SSE-KMS", + value: "sse-kms", + }, + ]} + /> + + + {encryptionType === "sse-kms" && ( + + ) => { + setKmsKeyID(e.target.value); + }} + id="select-kms-key-id" + name="select-kms-key-id" + label={"KMS Key ID"} + value={kmsKeyID} + options={keys.map((key: any) => { + return { + label: key.name, + value: key.name, + }; + })} + /> + + + +