TLS Certificate React component (#1780)

Add support to parse multiple blocks on pem certificate
Added react tls certificate component

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
Lenin Alevski
2022-03-31 09:56:39 -07:00
committed by GitHub
parent bf461b8b27
commit fba9bd87df
5 changed files with 278 additions and 315 deletions

View File

@@ -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)

View File

@@ -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 <http://www.gnu.org/licenses/>.
import * as React from "react";
import { SVGProps } from "react";
const CertificateIcon = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={`min-icon`}
fill={"currentcolor"}
viewBox="0 0 256 256"
{...props}
>
<defs>
<clipPath id="certificate_svg__a">
<path
data-name="Rect\xE1ngulo 2156"
fill="#07193e"
d="M0 0h256v222.048H0z"
/>
</clipPath>
</defs>
<g
data-name="Grupo 4763"
transform="translate(0 17)"
clipPath="url(#certificate_svg__a)"
fill="#07193e"
>
<path
data-name="Trazado 8152"
d="M240-.002H16a16 16 0 0 0-16 16v160a16 16 0 0 0 16 16h120l4.64-5.6 7.44-9.12A66.72 66.72 0 0 1 256 98.958v-82.96a16 16 0 0 0-16-16m-130.96 149.7H47.3a7.3 7.3 0 1 1 0-14.592h61.74a7.3 7.3 0 1 1 0 14.592m0-56H47.3a7.3 7.3 0 1 1 0-14.592h61.74a7.3 7.3 0 0 1 0 14.592m66.96-39.3a6.419 6.419 0 0 1-6.4 6.4H46.4a6.419 6.419 0 0 1-6.4-6.4v-1.792a6.419 6.419 0 0 1 6.4-6.4h123.2a6.419 6.419 0 0 1 6.4 6.4Z"
/>
<path
data-name="Trazado 8153"
d="M256 137.486a50.96 50.96 0 1 0-86.16 36.72l-15.52 18.96 7.2 28.88 29.28-35.68a50.018 50.018 0 0 0 28.4 0l29.28 35.68 7.2-28.88-15.52-18.96a50.75 50.75 0 0 0 15.84-36.72m-50.928 29.688a29.688 29.688 0 0 1-.072-59.376h.072a29.688 29.688 0 0 1 0 59.376"
/>
</g>
<path data-name="Rect\xE1ngulo 2157" fill="none" d="M0 0h256v256H0z" />
</svg>
);
};
export default CertificateIcon;

View File

@@ -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 <http://www.gnu.org/licenses/>.
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 (
<Chip
key={certificateInfo.name}
variant="outlined"
color="primary"
className={classes.certificateWrapper}
label={
<Container>
<Grid item xs={1} className={classes.certificateIcon}>
<CertificateIcon />
</Grid>
<Grid item xs={11} className={classes.certificateInfo}>
<Typography variant="subtitle1" display="block" gutterBottom>
{certificateInfo.name}
</Typography>
<Box className={classes.certificateExpiry}>
<EventBusyIcon color="inherit" fontSize="small" />
&nbsp;
<span className={"label"}>Expiry:&nbsp;</span>
<span>
<Moment format="YYYY/MM/DD">{certificateInfo.expiry}</Moment>
</span>
</Box>
<Divider />
<br />
<Box className={classes.certificateDomains}>
<span className="label">{`${certificates.length} Domain (s):`}</span>
</Box>
<List className={classes.certificatesList}>
{certificates.map((dom) => (
<ListItem className={classes.certificatesListItem}>
<ListItemAvatar>
<LanguageIcon />
</ListItemAvatar>
<ListItemText primary={dom} />
</ListItem>
))}
</List>
</Grid>
</Container>
}
onDelete={onDelete}
/>
);
};
export default withStyles(styles)(TLSCertificate);

View File

@@ -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)
</legend>
{vaultClientCertificateSecret ? (
<Chip
key={vaultClientCertificateSecret.name}
variant="outlined"
color="primary"
className={classes.certificateInfo}
label={
<div>
<Typography
variant="subtitle1"
display="block"
gutterBottom
>
{vaultClientCertificateSecret.name}
</Typography>
<Typography
className={classes.italic}
variant="caption"
display="block"
gutterBottom
>
{vaultClientCertificateSecret.domains &&
vaultClientCertificateSecret.domains.map(
(dom) => {
return <div>{dom}</div>;
}
)}
</Typography>
<Typography
className={classes.bold}
variant="overline"
gutterBottom
>
Expiry:&nbsp;
</Typography>
<Typography variant="caption" gutterBottom>
<Moment format="YYYY-MM-DD">
{vaultClientCertificateSecret.expiry}
</Moment>
</Typography>
</div>
}
<TLSCertificate
certificateInfo={vaultClientCertificateSecret}
onDelete={() =>
removeCertificate(vaultClientCertificateSecret)
}
@@ -1002,47 +962,8 @@ const TenantEncryption = ({
KMS CA certificate (optional)
</legend>
{vaultCACertificateSecret ? (
<Chip
key={vaultCACertificateSecret.name}
variant="outlined"
color="primary"
className={classes.certificateInfo}
label={
<div>
<Typography
variant="subtitle1"
display="block"
gutterBottom
>
{vaultCACertificateSecret.name}
</Typography>
<Typography
className={classes.italic}
variant="caption"
display="block"
gutterBottom
>
{vaultCACertificateSecret.domains &&
vaultCACertificateSecret.domains.map(
(dom) => {
return <div>{dom}</div>;
}
)}
</Typography>
<Typography
className={classes.bold}
variant="overline"
gutterBottom
>
Expiry:&nbsp;
</Typography>
<Typography variant="caption" gutterBottom>
<Moment format="YYYY-MM-DD">
{vaultCACertificateSecret.expiry}
</Moment>
</Typography>
</div>
}
<TLSCertificate
certificateInfo={vaultCACertificateSecret}
onDelete={() =>
removeCertificate(vaultCACertificateSecret)
}
@@ -1617,47 +1538,8 @@ const TenantEncryption = ({
value={""}
/>
{gemaltoCACertificateSecret ? (
<Chip
key={gemaltoCACertificateSecret.name}
variant="outlined"
color="primary"
className={classes.certificateInfo}
label={
<div>
<Typography
variant="subtitle1"
display="block"
gutterBottom
>
{gemaltoCACertificateSecret.name}
</Typography>
<Typography
className={classes.italic}
variant="caption"
display="block"
gutterBottom
>
{gemaltoCACertificateSecret.domains &&
gemaltoCACertificateSecret.domains.map(
(dom) => {
return <div>{dom}</div>;
}
)}
</Typography>
<Typography
className={classes.bold}
variant="overline"
gutterBottom
>
Expiry:&nbsp;
</Typography>
<Typography variant="caption" gutterBottom>
<Moment format="YYYY-MM-DD">
{gemaltoCACertificateSecret.expiry}
</Moment>
</Typography>
</div>
}
<TLSCertificate
certificateInfo={gemaltoCACertificateSecret}
onDelete={() =>
removeCertificate(gemaltoCACertificateSecret)
}
@@ -1709,47 +1591,8 @@ const TenantEncryption = ({
Encryption Service Certificates
</legend>
{serverTLSCertificateSecret ? (
<Chip
key={serverTLSCertificateSecret.name}
variant="outlined"
color="primary"
className={classes.certificateInfo}
label={
<div>
<Typography
variant="subtitle1"
display="block"
gutterBottom
>
{serverTLSCertificateSecret.name}
</Typography>
<Typography
className={classes.italic}
variant="caption"
display="block"
gutterBottom
>
{serverTLSCertificateSecret.domains &&
serverTLSCertificateSecret.domains.map(
(dom) => {
return <div>{dom}</div>;
}
)}
</Typography>
<Typography
className={classes.bold}
variant="overline"
gutterBottom
>
Expiry:&nbsp;
</Typography>
<Typography variant="caption" gutterBottom>
<Moment format="YYYY-MM-DD">
{serverTLSCertificateSecret.expiry}
</Moment>
</Typography>
</div>
}
<TLSCertificate
certificateInfo={serverTLSCertificateSecret}
onDelete={() =>
removeCertificate(serverTLSCertificateSecret)
}
@@ -1804,45 +1647,8 @@ const TenantEncryption = ({
Mutual TLS authentication with MinIO
</legend>
{mTLSCertificateSecret ? (
<Chip
key={mTLSCertificateSecret.name}
variant="outlined"
color="primary"
className={classes.certificateInfo}
label={
<div>
<Typography
variant="subtitle1"
display="block"
gutterBottom
>
{mTLSCertificateSecret.name}
</Typography>
<Typography
className={classes.italic}
variant="caption"
display="block"
gutterBottom
>
{mTLSCertificateSecret.domains &&
mTLSCertificateSecret.domains.map((dom) => {
return <div>{dom}</div>;
})}
</Typography>
<Typography
className={classes.bold}
variant="overline"
gutterBottom
>
Expiry:&nbsp;
</Typography>
<Typography variant="caption" gutterBottom>
<Moment format="YYYY-MM-DD">
{mTLSCertificateSecret.expiry}
</Moment>
</Typography>
</div>
}
<TLSCertificate
certificateInfo={mTLSCertificateSecret}
onDelete={() =>
removeCertificate(mTLSCertificateSecret)
}

View File

@@ -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 = ({
<Grid container item xs={12}>
{minioTLSCertificateSecrets.map(
(certificateInfo: ICertificateInfo) => (
<Chip
key={certificateInfo.name}
variant="outlined"
color="primary"
className={classes.certificateInfo}
label={
<div>
<Typography
variant="subtitle1"
display="block"
gutterBottom
>
{certificateInfo.name}
</Typography>
<Typography
className={classes.italic}
variant="caption"
display="block"
gutterBottom
>
{certificateInfo.domains &&
certificateInfo.domains.map((dom) => {
return <div key={`domain-${dom}`}>{dom}</div>;
})}
</Typography>
<Typography
className={classes.bold}
variant="overline"
gutterBottom
>
Expiry:&nbsp;
</Typography>
<Typography variant="caption" gutterBottom>
<Moment format="YYYY-MM-DD">
{certificateInfo.expiry}
</Moment>
</Typography>
</div>
}
<TLSCertificate
certificateInfo={certificateInfo}
onDelete={() => removeCertificate(certificateInfo)}
/>
)
@@ -508,47 +466,8 @@ const TenantSecurity = ({
<Grid container item xs={12}>
{minioTLSCaCertificateSecrets.map(
(certificateInfo: ICertificateInfo) => (
<Chip
key={certificateInfo.name}
variant="outlined"
color="primary"
className={classes.certificateInfo}
label={
<div>
<Typography
variant="subtitle1"
display="block"
gutterBottom
>
{certificateInfo.name}
</Typography>
<Typography
className={classes.italic}
variant="caption"
display="block"
gutterBottom
>
{certificateInfo.domains &&
certificateInfo.domains.map((dom) => {
return (
<div key={`CA-domain-${dom}`}>{dom}</div>
);
})}
</Typography>
<Typography
className={classes.bold}
variant="overline"
gutterBottom
>
Expiry:&nbsp;
</Typography>
<Typography variant="caption" gutterBottom>
<Moment format="YYYY-MM-DD">
{certificateInfo.expiry}
</Moment>
</Typography>
</div>
}
<TLSCertificate
certificateInfo={certificateInfo}
onDelete={() => removeCertificate(certificateInfo)}
/>
)