diff --git a/operatorapi/tenants.go b/operatorapi/tenants.go index f3734a48d..b12efbdc7 100644 --- a/operatorapi/tenants.go +++ b/operatorapi/tenants.go @@ -572,32 +572,44 @@ func parseTenantCertificates(ctx context.Context, clientSet K8sClientI, namespac } // Extract public key from certificate TLS secret if rawCert, ok := keyPair.Data[publicKey]; ok { - block, _ := pem.Decode(rawCert) - if block == nil { - // If certificate failed to decode skip - continue + var blocks []byte + for { + var block *pem.Block + block, rawCert = pem.Decode(rawCert) + if block == nil { + break + } + if block.Type == "CERTIFICATE" { + blocks = append(blocks, block.Bytes...) + } } - cert, err := x509.ParseCertificate(block.Bytes) + // parse all certificates we found on this k8s secret + certs, err := x509.ParseCertificates(blocks) if err != nil { return nil, err } - domains := []string{} - // append certificate domain names - if len(cert.DNSNames) > 0 { - domains = append(domains, cert.DNSNames...) - } - // append certificate IPs - if len(cert.IPAddresses) > 0 { - for _, ip := range cert.IPAddresses { - domains = append(domains, ip.String()) + for _, cert := range certs { + var domains []string + if cert.Subject.CommonName != "" { + domains = append(domains, cert.Subject.CommonName) } + // append certificate domain names + if len(cert.DNSNames) > 0 { + domains = append(domains, cert.DNSNames...) + } + // append certificate IPs + if len(cert.IPAddresses) > 0 { + for _, ip := range cert.IPAddresses { + domains = append(domains, ip.String()) + } + } + certificates = append(certificates, &models.CertificateInfo{ + SerialNumber: cert.SerialNumber.String(), + Name: secret.Name, + Domains: domains, + Expiry: cert.NotAfter.Format(time.RFC3339), + }) } - certificates = append(certificates, &models.CertificateInfo{ - SerialNumber: cert.SerialNumber.String(), - Name: secret.Name, - Domains: domains, - Expiry: cert.NotAfter.Format(time.RFC3339), - }) } } return certificates, nil @@ -627,8 +639,6 @@ func getTenantSecurityResponse(session *models.Principal, params operator_api.Te // 5 seconds timeout ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - //ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - //defer cancel() opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken) if err != nil { return nil, prepareError(err) diff --git a/portal-ui/src/icons/CertificateIcon.tsx b/portal-ui/src/icons/CertificateIcon.tsx new file mode 100644 index 000000000..d75d7e80d --- /dev/null +++ b/portal-ui/src/icons/CertificateIcon.tsx @@ -0,0 +1,57 @@ +// 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 CertificateIcon = (props: SVGProps) => { + return ( + + + + + + + + + + + + + ); +}; +export default CertificateIcon; diff --git a/portal-ui/src/screens/Console/Common/TLSCertificate/TLSCertificate.tsx b/portal-ui/src/screens/Console/Common/TLSCertificate/TLSCertificate.tsx new file mode 100644 index 000000000..9a79b5604 --- /dev/null +++ b/portal-ui/src/screens/Console/Common/TLSCertificate/TLSCertificate.tsx @@ -0,0 +1,171 @@ +// 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 React from "react"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import { ICertificateInfo } from "../../Tenants/types"; +import LanguageIcon from "@mui/icons-material/Language"; +import Chip from "@mui/material/Chip"; +import { + Typography, + Divider, + Box, + Grid, + Container, + ListItemText, + List, + ListItem, + ListItemAvatar, +} from "@mui/material"; +import EventBusyIcon from "@mui/icons-material/EventBusy"; +import Moment from "react-moment"; +import CertificateIcon from "../../../../icons/CertificateIcon"; + +const styles = (theme: Theme) => + createStyles({ + root: { + padding: 0, + margin: 0, + border: 0, + backgroundColor: "transparent", + textDecoration: "underline", + cursor: "pointer", + fontSize: "inherit", + color: theme.palette.info.main, + fontFamily: "Lato, sans-serif", + }, + certificateIcon: { + float: "left", + paddingTop: "5px !important", + paddingRight: "10px !important", + }, + certificateInfo: { float: "right" }, + certificateWrapper: { + height: "auto", + margin: 5, + border: "1px solid #E2E2E2", + userSelect: "text", + borderRadius: 4, + "& h6": { + fontWeight: "bold", + }, + "& div": { + padding: 0, + }, + }, + certificateExpiry: { + color: "#616161", + display: "flex", + alignItems: "center", + flexWrap: "wrap", + marginBottom: 5, + "& .label": { + fontWeight: "bold", + }, + }, + certificateDomains: { + color: "#616161", + "& .label": { + fontWeight: "bold", + }, + }, + certificatesList: { + border: "1px solid #E2E2E2", + borderRadius: 4, + color: "#616161", + textTransform: "lowercase", + overflowY: "scroll", + maxHeight: 145, + marginBottom: 10, + }, + certificatesListItem: { + padding: "0px 16px", + borderBottom: "1px solid #E2E2E2", + "& div": { + minWidth: 0, + }, + "& svg": { + fontSize: 12, + marginRight: 10, + opacity: 0.5, + }, + "& span": { + fontSize: 12, + }, + }, + }); + +interface ITLSCertificate { + classes: any; + certificateInfo: ICertificateInfo; + onDelete: any; +} + +const TLSCertificate = ({ + classes, + certificateInfo, + onDelete = () => {}, +}: ITLSCertificate) => { + const certificates = certificateInfo.domains || []; + return ( + + + + + + + {certificateInfo.name} + + + +   + Expiry:  + + {certificateInfo.expiry} + + + +
+ + {`${certificates.length} Domain (s):`} + + + {certificates.map((dom) => ( + + + + + + + ))} + +
+ + } + onDelete={onDelete} + /> + ); +}; + +export default withStyles(styles)(TLSCertificate); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantEncryption.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantEncryption.tsx index f7e40826b..a5f7382d7 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantEncryption.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantEncryption.tsx @@ -46,9 +46,7 @@ import Grid from "@mui/material/Grid"; import FileSelector from "../../Common/FormComponents/FileSelector/FileSelector"; import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; import RadioGroupSelector from "../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector"; -import { Button, DialogContentText, Typography } from "@mui/material"; -import Chip from "@mui/material/Chip"; -import Moment from "react-moment"; +import { Button, DialogContentText } from "@mui/material"; import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye"; import { KeyPair } from "../ListTenants/utils"; @@ -58,6 +56,7 @@ import { IValidation, } from "../../../../utils/validationFunctions"; import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog"; +import TLSCertificate from "../../Common/TLSCertificate/TLSCertificate"; interface ITenantEncryption { classes: any; @@ -913,47 +912,8 @@ const TenantEncryption = ({ Mutual TLS authentication with KMS (optional) {vaultClientCertificateSecret ? ( - - - {vaultClientCertificateSecret.name} - - - {vaultClientCertificateSecret.domains && - vaultClientCertificateSecret.domains.map( - (dom) => { - return
{dom}
; - } - )} -
- - Expiry:  - - - - {vaultClientCertificateSecret.expiry} - - - - } + removeCertificate(vaultClientCertificateSecret) } @@ -1002,47 +962,8 @@ const TenantEncryption = ({ KMS CA certificate (optional) {vaultCACertificateSecret ? ( - - - {vaultCACertificateSecret.name} - - - {vaultCACertificateSecret.domains && - vaultCACertificateSecret.domains.map( - (dom) => { - return
{dom}
; - } - )} -
- - Expiry:  - - - - {vaultCACertificateSecret.expiry} - - - - } + removeCertificate(vaultCACertificateSecret) } @@ -1617,47 +1538,8 @@ const TenantEncryption = ({ value={""} /> {gemaltoCACertificateSecret ? ( - - - {gemaltoCACertificateSecret.name} - - - {gemaltoCACertificateSecret.domains && - gemaltoCACertificateSecret.domains.map( - (dom) => { - return
{dom}
; - } - )} -
- - Expiry:  - - - - {gemaltoCACertificateSecret.expiry} - - - - } + removeCertificate(gemaltoCACertificateSecret) } @@ -1709,47 +1591,8 @@ const TenantEncryption = ({ Encryption Service Certificates {serverTLSCertificateSecret ? ( - - - {serverTLSCertificateSecret.name} - - - {serverTLSCertificateSecret.domains && - serverTLSCertificateSecret.domains.map( - (dom) => { - return
{dom}
; - } - )} -
- - Expiry:  - - - - {serverTLSCertificateSecret.expiry} - - - - } + removeCertificate(serverTLSCertificateSecret) } @@ -1804,45 +1647,8 @@ const TenantEncryption = ({ Mutual TLS authentication with MinIO {mTLSCertificateSecret ? ( - - - {mTLSCertificateSecret.name} - - - {mTLSCertificateSecret.domains && - mTLSCertificateSecret.domains.map((dom) => { - return
{dom}
; - })} -
- - Expiry:  - - - - {mTLSCertificateSecret.expiry} - - - - } + removeCertificate(mTLSCertificateSecret) } diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSecurity.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSecurity.tsx index e653e9f22..624026a2e 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSecurity.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/TenantSecurity.tsx @@ -26,11 +26,9 @@ import { } from "../../Common/FormComponents/common/styleLibrary"; import Paper from "@mui/material/Paper"; import Grid from "@mui/material/Grid"; -import Chip from "@mui/material/Chip"; import React, { Fragment, useCallback, useEffect, useState } from "react"; -import Moment from "react-moment"; import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; -import { Button, DialogContentText, Typography } from "@mui/material"; +import { Button, DialogContentText } from "@mui/material"; import { KeyPair } from "../ListTenants/utils"; import FileSelector from "../../Common/FormComponents/FileSelector/FileSelector"; import api from "../../../../common/api"; @@ -42,6 +40,7 @@ import { setTenantDetailsLoad } from "../actions"; import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog"; import { AddIcon, ConfirmModalIcon } from "../../../../icons"; import Loader from "../../Common/Loader/Loader"; +import TLSCertificate from "../../Common/TLSCertificate/TLSCertificate"; interface ITenantSecurity { classes: any; @@ -66,10 +65,6 @@ const styles = (theme: Theme) => paperContainer: { padding: "15px 15px 15px 50px", }, - certificateInfo: { - height: "auto", - margin: 5, - }, fileItem: { marginRight: 10, display: "flex", @@ -388,45 +383,8 @@ const TenantSecurity = ({ {minioTLSCertificateSecrets.map( (certificateInfo: ICertificateInfo) => ( - - - {certificateInfo.name} - - - {certificateInfo.domains && - certificateInfo.domains.map((dom) => { - return
{dom}
; - })} -
- - Expiry:  - - - - {certificateInfo.expiry} - - - - } + removeCertificate(certificateInfo)} /> ) @@ -508,47 +466,8 @@ const TenantSecurity = ({ {minioTLSCaCertificateSecrets.map( (certificateInfo: ICertificateInfo) => ( - - - {certificateInfo.name} - - - {certificateInfo.domains && - certificateInfo.domains.map((dom) => { - return ( -
{dom}
- ); - })} -
- - Expiry:  - - - - {certificateInfo.expiry} - - - - } + removeCertificate(certificateInfo)} /> )