From 7ad1b21e580f4cb876d36b8de0d3358f6bf0b43f Mon Sep 17 00:00:00 2001 From: Lucas Ponce Date: Wed, 20 May 2020 09:59:46 +0200 Subject: [PATCH 01/12] Add {Peer,Request}Authentication objects to Create Istio Config --- .../IstioWizards/IstioWizardActions.ts | 98 +++++- .../MessageCenter/AlertDrawerMessage.tsx | 2 +- .../IstioConfigNew/IstioConfigNewPage.tsx | 187 ++++++---- .../IstioConfigNew/PeerAuthenticationForm.tsx | 320 ++++++++++++++++++ .../RequestAuthenticationForm.tsx | 174 ++++++++++ .../JwtRuleBuilder.tsx | 272 +++++++++++++++ .../RequestAuthorizationForm/JwtRuleList.tsx | 126 +++++++ src/types/IstioObjects.ts | 2 +- 8 files changed, 1110 insertions(+), 71 deletions(-) create mode 100644 src/pages/IstioConfigNew/PeerAuthenticationForm.tsx create mode 100644 src/pages/IstioConfigNew/RequestAuthenticationForm.tsx create mode 100644 src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx create mode 100644 src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleList.tsx diff --git a/src/components/IstioWizards/IstioWizardActions.ts b/src/components/IstioWizards/IstioWizardActions.ts index d1d8b06839..8fb274a9e8 100644 --- a/src/components/IstioWizards/IstioWizardActions.ts +++ b/src/components/IstioWizards/IstioWizardActions.ts @@ -10,17 +10,22 @@ import { Condition, DestinationRule, DestinationRules, - HTTPRouteDestination, Gateway, HTTPMatchRequest, HTTPRoute, + HTTPRouteDestination, LoadBalancerSettings, Operation, + PeerAuthentication, + PeerAuthenticationMutualTLSMode, + PeerAuthenticationWorkloadSelector, + RequestAuthentication, Sidecar, Source, StringMatch, VirtualService, - VirtualServices + VirtualServices, + WorkloadEntrySelector } from '../../types/IstioObjects'; import { serverConfig } from '../../config'; import { ThreeScaleServiceRule } from '../../types/ThreeScale'; @@ -29,6 +34,8 @@ import { ConsistentHashType, MUTUAL, TrafficPolicyState } from './TrafficPolicy' import { GatewayState } from '../../pages/IstioConfigNew/GatewayForm'; import { SidecarState } from '../../pages/IstioConfigNew/SidecarForm'; import { AuthorizationPolicyState } from '../../pages/IstioConfigNew/AuthorizationPolicyForm'; +import { PeerAuthenticationState } from '../../pages/IstioConfigNew/PeerAuthenticationForm'; +import { RequestAuthenticationState } from '../../pages/IstioConfigNew/RequestAuthenticationForm'; export const WIZARD_WEIGHTED_ROUTING = 'weighted_routing'; export const WIZARD_MATCHING_ROUTING = 'matching_routing'; @@ -765,6 +772,93 @@ export const buildGateway = (name: string, namespace: string, state: GatewayStat return gw; }; +export const buildPeerAuthentication = ( + name: string, + namespace: string, + state: PeerAuthenticationState +): PeerAuthentication => { + const pa: PeerAuthentication = { + metadata: { + name: name, + namespace: namespace, + labels: { + [KIALI_WIZARD_LABEL]: 'PeerAuthentication' + } + }, + spec: {} + }; + + if (state.workloadSelector.length > 0) { + const workloadSelector: PeerAuthenticationWorkloadSelector = { + matchLabels: {} + }; + state.workloadSelector.split(',').forEach(label => { + label = label.trim(); + const labelDetails = label.split('='); + if (labelDetails.length === 2) { + workloadSelector.matchLabels[labelDetails[0]] = labelDetails[1]; + } + }); + pa.spec.selector = workloadSelector; + } + + // Kiali is always adding this field + pa.spec.mtls = { + mode: PeerAuthenticationMutualTLSMode[state.mtls] + }; + + if (state.portLevelMtls.length > 0) { + pa.spec.portLevelMtls = {}; + state.portLevelMtls.forEach(p => { + if (pa.spec.portLevelMtls) { + pa.spec.portLevelMtls[Number(p.port)] = { + mode: PeerAuthenticationMutualTLSMode[p.mtls] + }; + } + }); + } + + return pa; +}; + +export const buildRequestAuthentication = ( + name: string, + namespace: string, + state: RequestAuthenticationState +): RequestAuthentication => { + const ra: RequestAuthentication = { + metadata: { + name: name, + namespace: namespace, + labels: { + [KIALI_WIZARD_LABEL]: 'RequestAuthentication' + } + }, + spec: { + jwtRules: [] + } + }; + + if (state.workloadSelector.length > 0) { + const workloadSelector: WorkloadEntrySelector = { + matchLabels: {} + }; + state.workloadSelector.split(',').forEach(label => { + label = label.trim(); + const labelDetails = label.split('='); + if (labelDetails.length === 2) { + workloadSelector.matchLabels[labelDetails[0]] = labelDetails[1]; + } + }); + ra.spec.selector = workloadSelector; + } + + if (state.jwtRules.length > 0) { + ra.spec.jwtRules = state.jwtRules; + } + return ra; +}; + export const buildSidecar = (name: string, namespace: string, state: SidecarState): Sidecar => { const sc: Sidecar = { metadata: { diff --git a/src/components/MessageCenter/AlertDrawerMessage.tsx b/src/components/MessageCenter/AlertDrawerMessage.tsx index 590eb21eb3..15ebad2ae0 100644 --- a/src/components/MessageCenter/AlertDrawerMessage.tsx +++ b/src/components/MessageCenter/AlertDrawerMessage.tsx @@ -57,7 +57,7 @@ class AlertDrawerMessage extends React.PureComponent { onToggle={() => this.props.toggleMessageDetail(this.props.message)} isExpanded={this.props.message.showDetail} > -
{this.props.message.detail}
+
{this.props.message.detail}
)} {this.props.message.count > 1 && ( diff --git a/src/pages/IstioConfigNew/IstioConfigNewPage.tsx b/src/pages/IstioConfigNew/IstioConfigNewPage.tsx index acb1e3b649..47cb5062a6 100644 --- a/src/pages/IstioConfigNew/IstioConfigNewPage.tsx +++ b/src/pages/IstioConfigNew/IstioConfigNewPage.tsx @@ -14,12 +14,23 @@ import * as API from '../../services/Api'; import { IstioPermissions } from '../../types/IstioConfigDetails'; import * as AlertUtils from '../../utils/AlertUtils'; import history from '../../app/History'; -import { buildAuthorizationPolicy, buildGateway, buildSidecar } from '../../components/IstioWizards/IstioWizardActions'; +import { + buildAuthorizationPolicy, + buildGateway, + buildPeerAuthentication, + buildRequestAuthentication, + buildSidecar +} from '../../components/IstioWizards/IstioWizardActions'; import { MessageType } from '../../types/MessageCenter'; import AuthorizationPolicyForm, { AuthorizationPolicyState, INIT_AUTHORIZATION_POLICY } from './AuthorizationPolicyForm'; +import PeerAuthenticationForm, { INIT_PEER_AUTHENTICATION, PeerAuthenticationState } from './PeerAuthenticationForm'; +import RequestAuthenticationForm, { + INIT_REQUEST_AUTHENTICATION, + RequestAuthenticationState +} from './RequestAuthenticationForm'; type Props = { activeNamespaces: Namespace[]; @@ -31,6 +42,8 @@ type State = { istioPermissions: IstioPermissions; authorizationPolicy: AuthorizationPolicyState; gateway: GatewayState; + peerAuthentication: PeerAuthenticationState; + requestAuthentication: RequestAuthenticationState; sidecar: SidecarState; }; @@ -40,18 +53,26 @@ const AUTHORIZACION_POLICY = 'AuthorizationPolicy'; const AUTHORIZATION_POLICIES = 'authorizationpolicies'; const GATEWAY = 'Gateway'; const GATEWAYS = 'gateways'; +const PEER_AUTHENTICATION = 'PeerAuthentication'; +const PEER_AUTHENTICATIONS = 'peerauthentications'; +const REQUEST_AUTHENTICATION = 'RequestAuthentication'; +const REQUEST_AUTHENTICATIONS = 'requestauthentications'; const SIDECAR = 'Sidecar'; const SIDECARS = 'sidecars'; const DIC = { AuthorizationPolicy: AUTHORIZATION_POLICIES, Gateway: GATEWAYS, + PeerAuthentication: PEER_AUTHENTICATIONS, + RequestAuthentication: REQUEST_AUTHENTICATIONS, Sidecar: SIDECARS }; const istioResourceOptions = [ { value: AUTHORIZACION_POLICY, label: AUTHORIZACION_POLICY, disabled: false }, { value: GATEWAY, label: GATEWAY, disabled: false }, + { value: PEER_AUTHENTICATION, label: PEER_AUTHENTICATION, disabled: false }, + { value: REQUEST_AUTHENTICATION, label: REQUEST_AUTHENTICATION, disabled: false }, { value: SIDECAR, label: SIDECAR, disabled: false } ]; @@ -63,6 +84,8 @@ const INIT_STATE = (): State => ({ gateway: { gatewayServers: [] }, + peerAuthentication: INIT_PEER_AUTHENTICATION(), + requestAuthentication: INIT_REQUEST_AUTHENTICATION(), sidecar: { egressHosts: [ // Init with the istio-system/* for sidecar @@ -149,74 +172,56 @@ class IstioConfigNewPage extends React.Component { }; onIstioResourceCreate = () => { - switch (this.state.istioResource) { - case AUTHORIZACION_POLICY: - this.promises - .registerAll( - 'Create AuthorizationPolicies', - this.props.activeNamespaces.map(ns => - API.createIstioConfigDetail( - ns.name, - 'authorizationpolicies', - JSON.stringify(buildAuthorizationPolicy(this.state.name, ns.name, this.state.authorizationPolicy)) - ) - ) - ) - .then(results => { - if (results.length > 0) { - AlertUtils.add('Istio AuthorizationPolicy created', 'default', MessageType.SUCCESS); - } - this.backToList(); - }) - .catch(error => { - AlertUtils.addError('Could not create Istio AuthorizationPolicy objects.', error); + const jsonIstioObjects: { namespace: string; json: string }[] = []; + this.props.activeNamespaces.map(ns => { + switch (this.state.istioResource) { + case AUTHORIZACION_POLICY: + jsonIstioObjects.push({ + namespace: ns.name, + json: JSON.stringify(buildAuthorizationPolicy(this.state.name, ns.name, this.state.authorizationPolicy)) }); - break; - case GATEWAY: - this.promises - .registerAll( - 'Create Gateways', - this.props.activeNamespaces.map(ns => - API.createIstioConfigDetail( - ns.name, - 'gateways', - JSON.stringify(buildGateway(this.state.name, ns.name, this.state.gateway)) - ) - ) - ) - .then(results => { - if (results.length > 0) { - AlertUtils.add('Istio Gateway created', 'default', MessageType.SUCCESS); - } - this.backToList(); - }) - .catch(error => { - AlertUtils.addError('Could not create Istio Gateway objects.', error); + break; + case GATEWAY: + jsonIstioObjects.push({ + namespace: ns.name, + json: JSON.stringify(buildGateway(this.state.name, ns.name, this.state.gateway)) }); - break; - case SIDECAR: - this.promises - .registerAll( - 'Create Sidecars', - this.props.activeNamespaces.map(ns => - API.createIstioConfigDetail( - ns.name, - 'sidecars', - JSON.stringify(buildSidecar(this.state.name, ns.name, this.state.sidecar)) - ) - ) - ) - .then(results => { - if (results.length > 0) { - AlertUtils.add('Istio Sidecar created', 'default', MessageType.SUCCESS); - } - this.backToList(); - }) - .catch(error => { - AlertUtils.addError('Could not create Istio Sidecar objects.', error); + break; + case PEER_AUTHENTICATION: + jsonIstioObjects.push({ + namespace: ns.name, + json: JSON.stringify(buildPeerAuthentication(this.state.name, ns.name, this.state.peerAuthentication)) }); - break; - } + break; + case REQUEST_AUTHENTICATION: + jsonIstioObjects.push({ + namespace: ns.name, + json: JSON.stringify(buildRequestAuthentication(this.state.name, ns.name, this.state.requestAuthentication)) + }); + break; + case SIDECAR: + jsonIstioObjects.push({ + namespace: ns.name, + json: JSON.stringify(buildSidecar(this.state.name, ns.name, this.state.sidecar)) + }); + break; + } + }); + + this.promises + .registerAll( + 'Create ' + DIC[this.state.istioResource], + jsonIstioObjects.map(o => API.createIstioConfigDetail(o.namespace, DIC[this.state.istioResource], o.json)) + ) + .then(results => { + if (results.length > 0) { + AlertUtils.add('Istio ' + this.state.istioResource + ' created', 'default', MessageType.SUCCESS); + } + this.backToList(); + }) + .catch(error => { + AlertUtils.addError('Could not create Istio ' + this.state.istioResource + ' objects.', error); + }); }; backToList = () => { @@ -234,6 +239,19 @@ class IstioConfigNewPage extends React.Component { return this.state.istioResource === GATEWAY && this.state.gateway.gatewayServers.length > 0; }; + isPeerAuthenticationValid = (): boolean => { + const validPortsMtls = + this.state.peerAuthentication.portLevelMtls.length > 0 && + this.state.peerAuthentication.workloadSelector.length === 0 + ? false + : true; + return this.state.istioResource === PEER_AUTHENTICATION && validPortsMtls; + }; + + isRequestAuthenticationValid = (): boolean => { + return this.state.istioResource === REQUEST_AUTHENTICATION; + }; + isSidecarValid = (): boolean => { return ( this.state.istioResource === SIDECAR && @@ -255,6 +273,25 @@ class IstioConfigNewPage extends React.Component { }); }; + onChangePeerAuthentication = (peerAuthentication: PeerAuthenticationState) => { + this.setState(prevState => { + prevState.peerAuthentication.workloadSelector = peerAuthentication.workloadSelector; + return { + peerAuthentication: prevState.peerAuthentication + }; + }); + }; + + onChangeRequestAuthentication = (requestAuthentication: RequestAuthenticationState) => { + this.setState(prevState => { + prevState.requestAuthentication.workloadSelector = requestAuthentication.workloadSelector; + prevState.requestAuthentication.jwtRules = requestAuthentication.jwtRules; + return { + requestAuthentication: prevState.requestAuthentication + }; + }); + }; + render() { const canCreate = this.props.activeNamespaces.every(ns => this.canCreate(ns.name)); const isNameValid = this.state.name.length > 0; @@ -263,7 +300,11 @@ class IstioConfigNewPage extends React.Component { canCreate && isNameValid && isNamespacesValid && - (this.isGatewayValid() || this.isSidecarValid() || this.isAuthorizationPolicyValid()); + (this.isGatewayValid() || + this.isSidecarValid() || + this.isAuthorizationPolicyValid() || + this.isPeerAuthenticationValid() || + this.isRequestAuthenticationValid()); return (
@@ -348,6 +389,18 @@ class IstioConfigNewPage extends React.Component { }} /> )} + {this.state.istioResource === PEER_AUTHENTICATION && ( + + )} + {this.state.istioResource === REQUEST_AUTHENTICATION && ( + + )} {this.state.istioResource === SIDECAR && ( void; +}; + +export type PortMtls = { + port: string; + mtls: string; +}; + +export type PeerAuthenticationState = { + workloadSelector: string; + mtls: string; + portLevelMtls: PortMtls[]; +}; + +export const INIT_PEER_AUTHENTICATION = (): PeerAuthenticationState => ({ + workloadSelector: '', + mtls: PeerAuthenticationMutualTLSMode.UNSET, + portLevelMtls: [] +}); + +type State = { + addWorkloadSelector: boolean; + workloadSelectorValid: boolean; + workloadSelectorLabels: string; + mtls: string; + addPortMtls: boolean; + portLevelMtls: PortMtls[]; + addNewPortMtls: PortMtls; +}; + +class PeerAuthenticationForm extends React.Component { + constructor(props: Props) { + super(props); + this.state = { + addWorkloadSelector: false, + workloadSelectorValid: false, + workloadSelectorLabels: this.props.peerAuthentication.workloadSelector, + mtls: this.props.peerAuthentication.mtls, + addPortMtls: false, + portLevelMtls: this.props.peerAuthentication.portLevelMtls, + addNewPortMtls: { + port: '', + mtls: PeerAuthenticationMutualTLSMode.UNSET + } + }; + } + + addWorkloadLabels = (value: string, _) => { + if (value.length === 0) { + this.setState({ + workloadSelectorValid: false, + workloadSelectorLabels: '' + }); + return; + } + value = value.trim(); + const labels: string[] = value.split(','); + let isValid = true; + // Some smoke validation rules for the labels + for (let i = 0; i < labels.length; i++) { + const label = labels[i]; + if (label.indexOf('=') < 0) { + isValid = false; + break; + } + const splitLabel: string[] = label.split('='); + if (splitLabel.length !== 2) { + isValid = false; + break; + } + if (splitLabel[0].trim().length === 0 || splitLabel[1].trim().length === 0) { + isValid = false; + break; + } + } + this.setState( + { + workloadSelectorValid: isValid, + workloadSelectorLabels: value + }, + () => this.onPeerAuthenticationChange() + ); + }; + + onPeerAuthenticationChange = () => { + const peerAuthentication: PeerAuthenticationState = { + workloadSelector: this.state.workloadSelectorLabels, + mtls: this.state.mtls, + portLevelMtls: this.state.portLevelMtls + }; + this.props.onChange(peerAuthentication); + }; + + onMutualTlsChange = (value, _) => { + this.setState( + { + mtls: value + }, + () => this.onPeerAuthenticationChange() + ); + }; + + onAddPortNumber = (value: string, _) => { + this.setState(prevState => ({ + addNewPortMtls: { + port: value.trim(), + mtls: prevState.addNewPortMtls.mtls + } + })); + }; + + onAddPortMtlsMode = (value: string, _) => { + this.setState(prevState => ({ + addNewPortMtls: { + port: prevState.addNewPortMtls.port, + mtls: value + } + })); + }; + + onAddPortMtls = () => { + this.setState( + prevState => { + prevState.portLevelMtls.push(prevState.addNewPortMtls); + return { + portLevelMtls: prevState.portLevelMtls, + addNewPortMtls: { + port: '', + mtls: PeerAuthenticationMutualTLSMode.UNSET + } + }; + }, + () => this.onPeerAuthenticationChange() + ); + }; + + // @ts-ignore + actionResolver = (rowData, { rowIndex }) => { + const removeAction = { + title: 'Remove Port MTLS', + // @ts-ignore + onClick: (event, rowIndex, rowData, extraData) => { + this.setState( + prevState => { + prevState.portLevelMtls.splice(rowIndex, 1); + return { + portLevelMtls: prevState.portLevelMtls + }; + }, + () => this.onPeerAuthenticationChange() + ); + } + }; + if (rowIndex < this.props.peerAuthentication.portLevelMtls.length) { + return [removeAction]; + } + return []; + }; + + rows() { + return this.props.peerAuthentication.portLevelMtls + .map((pmtls, i) => ({ + key: 'portMtls' + i, + cells: [<>{pmtls.port}, <>{pmtls.mtls}, ''] + })) + .concat([ + { + key: 'pmtlsNew', + cells: [ + <> + 0 && !isNaN(Number(this.state.addNewPortMtls.port))} + /> + , + <> + + {Object.keys(PeerAuthenticationMutualTLSMode).map((option, index) => ( + + ))} + + , + <> + + + ] + } + ]); + } + + render() { + return ( + <> + + { + this.setState(prevState => ({ + addWorkloadSelector: !prevState.addWorkloadSelector + })); + }} + /> + + {this.state.addWorkloadSelector && ( + + + + )} + + + {Object.keys(PeerAuthenticationMutualTLSMode).map((option, index) => ( + + ))} + + + + { + this.setState(prevState => ({ + addPortMtls: !prevState.addPortMtls + })); + }} + /> + + {this.state.addPortMtls && ( + + + + +
+ {this.props.peerAuthentication.portLevelMtls.length === 0 && ( +
PeerAuthentication has no Ports MTLS defined
+ )} + {!this.state.addWorkloadSelector && ( +
Ports MTLS require a Workload Selector
+ )} +
+ )} + + ); + } +} + +export default PeerAuthenticationForm; diff --git a/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx b/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx new file mode 100644 index 0000000000..30b319cc7b --- /dev/null +++ b/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx @@ -0,0 +1,174 @@ +import * as React from 'react'; +import { FormGroup, Switch } from '@patternfly/react-core'; +import { TextInputBase as TextInput } from '@patternfly/react-core/dist/js/components/TextInput/TextInput'; +import { JWTRule } from '../../types/IstioObjects'; +import JwtRuleBuilder from './RequestAuthorizationForm/JwtRuleBuilder'; +import JwtRuleList from './RequestAuthorizationForm/JwtRuleList'; + +type Props = { + requestAuthentication: RequestAuthenticationState; + onChange: (requestAuthentication: RequestAuthenticationState) => void; +}; + +export type RequestAuthenticationState = { + workloadSelector: string; + jwtRules: JWTRule[]; +}; + +export const INIT_REQUEST_AUTHENTICATION = (): RequestAuthenticationState => ({ + workloadSelector: '', + jwtRules: [] +}); + +type State = { + addWorkloadSelector: boolean; + workloadSelectorValid: boolean; + workloadSelectorLabels: string; + addJWTRules: boolean; + jwtRules: JWTRule[]; +}; + +class RequestAuthenticationForm extends React.Component { + constructor(props: Props) { + super(props); + this.state = { + addWorkloadSelector: false, + workloadSelectorValid: false, + workloadSelectorLabels: this.props.requestAuthentication.workloadSelector, + addJWTRules: false, + jwtRules: [] + }; + } + + addWorkloadLabels = (value: string, _) => { + if (value.length === 0) { + this.setState({ + workloadSelectorValid: false, + workloadSelectorLabels: '' + }); + return; + } + value = value.trim(); + const labels: string[] = value.split(','); + let isValid = true; + // Some smoke validation rules for the labels + for (let i = 0; i < labels.length; i++) { + const label = labels[i]; + if (label.indexOf('=') < 0) { + isValid = false; + break; + } + const splitLabel: string[] = label.split('='); + if (splitLabel.length !== 2) { + isValid = false; + break; + } + if (splitLabel[0].trim().length === 0 || splitLabel[1].trim().length === 0) { + isValid = false; + break; + } + } + this.setState( + { + workloadSelectorValid: isValid, + workloadSelectorLabels: value + }, + () => this.onRequestAuthenticationChange() + ); + }; + + onRequestAuthenticationChange = () => { + const requestAuthentication: RequestAuthenticationState = { + workloadSelector: this.state.workloadSelectorLabels, + jwtRules: this.state.jwtRules + }; + this.props.onChange(requestAuthentication); + }; + + onAddJwtRule = (jwtRule: JWTRule) => { + this.setState( + prevState => { + prevState.jwtRules.push(jwtRule); + return { + jwtRules: prevState.jwtRules + }; + }, + () => this.onRequestAuthenticationChange() + ); + }; + + onRemoveJwtRule = (index: number) => { + this.setState( + prevState => { + prevState.jwtRules.splice(index, 1); + return { + jwtRules: prevState.jwtRules + }; + }, + () => this.onRequestAuthenticationChange() + ); + }; + + render() { + return ( + <> + + { + this.setState(prevState => ({ + addWorkloadSelector: !prevState.addWorkloadSelector + })); + }} + /> + + {this.state.addWorkloadSelector && ( + + + + )} + + { + this.setState(prevState => ({ + addJWTRules: !prevState.addJWTRules + })); + }} + /> + + {this.state.addJWTRules && ( + <> + + + + + + + + )} + + ); + } +} + +export default RequestAuthenticationForm; diff --git a/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx b/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx new file mode 100644 index 0000000000..7f4113414f --- /dev/null +++ b/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx @@ -0,0 +1,272 @@ +import * as React from 'react'; +import { JWTHeader, JWTRule } from '../../../types/IstioObjects'; +import { cellWidth, ICell, Table, TableBody, TableHeader } from '@patternfly/react-table'; +import { Button, FormSelect, FormSelectOption } from '@patternfly/react-core'; +import { PlusCircleIcon } from '@patternfly/react-icons'; +import { TextInputBase as TextInput } from '@patternfly/react-core/dist/js/components/TextInput/TextInput'; + +type Props = { + onAddJwtRule: (rule: JWTRule) => void; +}; + +type State = { + jwtRuleFields: string[]; + jwtRule: JWTRule; + newJwtField: string; + newValues: string; +}; + +const INIT_JWT_RULE_FIELDS = [ + 'issuer', + 'audiences', + 'jwksUri', + 'jwks', + 'fromHeaders', + 'fromParams', + 'outputPayloadToHeader', + 'forwardOriginalToken' +].sort(); + +const headerCells: ICell[] = [ + { + title: 'JWT Rule Field', + transforms: [cellWidth(30) as any], + props: {} + }, + { + title: 'Values', + transforms: [cellWidth(70) as any], + props: {} + }, + { + title: '', + props: {} + } +]; + +export const formatJwtField = (jwtField: string, jwtRule: JWTRule): string => { + switch (jwtField) { + case 'issuer': + return jwtRule.issuer ? jwtRule.issuer : ''; + case 'audiences': + return jwtRule.audiences ? jwtRule.audiences.join(',') : ''; + case 'jwksUri': + return jwtRule.jwksUri ? jwtRule.jwksUri : ''; + case 'fromHeaders': + return jwtRule.fromHeaders + ? jwtRule.fromHeaders + .map(header => { + if (header.prefix) { + return header.name + ': ' + header.prefix; + } else { + return header.name; + } + }) + .join(',') + : ''; + case 'fromParams': + return jwtRule.fromParams ? jwtRule.fromParams.join(',') : ''; + case 'outputPayloadToHeader': + return jwtRule.outputPayloadToHeader ? jwtRule.outputPayloadToHeader : ''; + case 'forwardOriginalToken': + return jwtRule.forwardOriginalToken ? '' + jwtRule.forwardOriginalToken : ''; + default: + } + return ''; +}; + +class JwtRuleBuilder extends React.Component { + constructor(props: Props) { + super(props); + this.state = { + jwtRuleFields: Object.assign([], INIT_JWT_RULE_FIELDS), + jwtRule: {}, + newJwtField: 'issuer', + newValues: '' + }; + } + + onAddJwtField = (value: string, _) => { + this.setState({ + newJwtField: value + }); + }; + + onAddNewValues = (value: string, _) => { + this.setState({ + newValues: value + }); + }; + + onUpdateJwtRule = () => { + this.setState(prevState => { + const i = prevState.jwtRuleFields.indexOf(prevState.newJwtField); + if (i > -1) { + prevState.jwtRuleFields.splice(i, 1); + } + switch (prevState.newJwtField) { + case 'issuer': + prevState.jwtRule.issuer = prevState.newValues; + break; + case 'audiences': + prevState.jwtRule.audiences = prevState.newValues.split(','); + break; + case 'jwksUri': + prevState.jwtRule.jwksUri = prevState.newValues; + break; + case 'fromHeaders': + // Parse a string like: + // "Authorization: Bearer , Authorization: Bearer, Security " + // In [{name: 'Authorization', prefix: 'Bearer '}, {name: 'Authorization', prefix: 'Bearer'}, {name: 'Security}] + prevState.jwtRule.fromHeaders = []; + prevState.newValues.split(',').forEach(value => { + const values = value.split(':'); + const header: JWTHeader = { + name: values[0] + }; + if (values.length > 1) { + header.prefix = values[1].trimLeft(); + } + if (prevState.jwtRule.fromHeaders) { + prevState.jwtRule.fromHeaders.push(header); + } + }); + break; + case 'fromParams': + prevState.jwtRule.fromParams = prevState.newValues.split(','); + break; + case 'outputPayloadToHeader': + prevState.jwtRule.outputPayloadToHeader = prevState.newValues; + break; + case 'forwardOriginalToken': + // I don't want to put different types for input, perhaps in the future + prevState.jwtRule.forwardOriginalToken = prevState.newValues.toLowerCase() === 'true'; + break; + default: + // No default action. + } + return { + jwtRuleFields: prevState.jwtRuleFields, + jwtRule: prevState.jwtRule, + newJwtField: prevState.jwtRuleFields[0], + newValues: '' + }; + }); + }; + + onAddJwtRuleToList = () => { + const oldJwtRule = this.state.jwtRule; + this.setState( + { + jwtRuleFields: Object.assign([], INIT_JWT_RULE_FIELDS), + jwtRule: {}, + newJwtField: INIT_JWT_RULE_FIELDS[0], + newValues: '' + }, + () => this.props.onAddJwtRule(oldJwtRule) + ); + }; + + // @ts-ignore + actionResolver = (rowData, { rowIndex }) => { + const removeAction = { + title: 'Remove Field', + // @ts-ignore + onClick: (event, rowIndex, rowData, extraData) => { + // Fetch sourceField from rowData, it's a fixed string on children + const removeJwtRuleField = rowData.cells[0].props.children.toString(); + this.setState(prevState => { + prevState.jwtRuleFields.push(removeJwtRuleField); + delete prevState.jwtRule[removeJwtRuleField]; + const newJwtRuleFields = prevState.jwtRuleFields.sort(); + return { + jwtRuleFields: newJwtRuleFields, + jwtRule: prevState.jwtRule, + newJwtField: newJwtRuleFields[0], + newValues: '' + }; + }); + } + }; + if (rowIndex < Object.keys(this.state.jwtRule).length) { + return [removeAction]; + } + return []; + }; + + isJwtRuleValid = (): boolean => { + return this.state.jwtRule.issuer ? this.state.jwtRule.issuer.length > 0 : false; + }; + + rows = () => { + return Object.keys(this.state.jwtRule) + .map((jwtField, i) => { + return { + key: 'jwtField' + i, + cells: [<>{jwtField}, <>{formatJwtField(jwtField, this.state.jwtRule)}, <>] + }; + }) + .concat([ + { + key: 'jwtFieldKeyNew', + cells: [ + <> + + {this.state.jwtRuleFields.map((option, index) => ( + + ))} + + , + <> + + , + <> + {this.state.jwtRuleFields.length > 0 && ( + + + ); + } +} + +export default JwtRuleBuilder; diff --git a/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleList.tsx b/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleList.tsx new file mode 100644 index 0000000000..4f5df3c280 --- /dev/null +++ b/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleList.tsx @@ -0,0 +1,126 @@ +import { JWTRule } from '../../../types/IstioObjects'; +import { cellWidth, ICell, Table, TableBody, TableHeader } from '@patternfly/react-table'; +import { style } from 'typestyle'; +import { PfColors } from '../../../components/Pf/PfColors'; +import * as React from 'react'; +import { formatJwtField } from './JwtRuleBuilder'; + +type Props = { + jwtRules: JWTRule[]; + onRemoveJwtRule: (index: number) => void; +}; + +const headerCells: ICell[] = [ + { + title: 'JWT Rules to be validated', + transforms: [cellWidth(100) as any], + props: {} + }, + { + title: '', + props: {} + } +]; + +const noJWTRulesStyle = style({ + marginTop: 10, + color: PfColors.Red100, + textAlign: 'center', + width: '100%' +}); + +class JwtRuleList extends React.Component { + rows = () => { + return this.props.jwtRules.map((jwtRule, i) => { + return { + key: 'jwtRule' + i, + cells: [ + <> + {jwtRule.issuer ? ( +
+ issuer: [{formatJwtField('issuer', jwtRule)}] +
+ ) : ( + undefined + )} + {jwtRule.audiences ? ( +
+ audiences: [{formatJwtField('audiences', jwtRule)}] +
+ ) : ( + undefined + )} + {jwtRule.jwksUri ? ( +
+ jwksUri: [{formatJwtField('jwksUri', jwtRule)}] +
+ ) : ( + undefined + )} + {jwtRule.fromHeaders ? ( +
+ fromHeaders: [{formatJwtField('fromHeaders', jwtRule)}] +
+ ) : ( + undefined + )} + {jwtRule.fromParams ? ( +
+ fromParams: [{formatJwtField('fromParams', jwtRule)}] +
+ ) : ( + undefined + )} + {jwtRule.outputPayloadToHeader ? ( +
+ outputPayloadToHeader: [{formatJwtField('outputPayloadToHeader', jwtRule)}] +
+ ) : ( + undefined + )} + {jwtRule.forwardOriginalToken ? ( +
+ forwardOriginalToken: [{formatJwtField('forwardOriginalToken', jwtRule)}] +
+ ) : ( + undefined + )} + , + <> + ] + }; + }); + }; + + // @ts-ignore + actionResolver = (rowData, { rowIndex }) => { + const removeAction = { + title: 'Remove JWT Rule', + // @ts-ignore + onClick: (event, rowIndex, rowData, extraData) => { + this.props.onRemoveJwtRule(rowIndex); + } + }; + return [removeAction]; + }; + + render() { + return ( + <> + + + +
+ {this.props.jwtRules.length === 0 &&
No JWT Rules Defined
} + + ); + } +} + +export default JwtRuleList; diff --git a/src/types/IstioObjects.ts b/src/types/IstioObjects.ts index 8ca990cca4..0488685d39 100644 --- a/src/types/IstioObjects.ts +++ b/src/types/IstioObjects.ts @@ -884,7 +884,7 @@ export interface JWTHeader { } export interface JWTRule { - issuer: string; + issuer?: string; audiences?: string[]; jwksUri?: string; jwks?: string; From d37fb05cc6e63148bfa51869c3ee1a20083ec697 Mon Sep 17 00:00:00 2001 From: Lucas Ponce Date: Thu, 21 May 2020 14:17:11 +0200 Subject: [PATCH 02/12] Fix correct iteration method --- src/pages/IstioConfigNew/IstioConfigNewPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/IstioConfigNew/IstioConfigNewPage.tsx b/src/pages/IstioConfigNew/IstioConfigNewPage.tsx index 47cb5062a6..4dd1cb6620 100644 --- a/src/pages/IstioConfigNew/IstioConfigNewPage.tsx +++ b/src/pages/IstioConfigNew/IstioConfigNewPage.tsx @@ -173,7 +173,7 @@ class IstioConfigNewPage extends React.Component { onIstioResourceCreate = () => { const jsonIstioObjects: { namespace: string; json: string }[] = []; - this.props.activeNamespaces.map(ns => { + this.props.activeNamespaces.forEach(ns => { switch (this.state.istioResource) { case AUTHORIZACION_POLICY: jsonIstioObjects.push({ From 8989f85dd76586cff9436e85933fe978c76790d8 Mon Sep 17 00:00:00 2001 From: Lucas Ponce Date: Fri, 22 May 2020 11:06:01 +0200 Subject: [PATCH 03/12] Fix PeerAuthentication state --- src/pages/IstioConfigNew/IstioConfigNewPage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/IstioConfigNew/IstioConfigNewPage.tsx b/src/pages/IstioConfigNew/IstioConfigNewPage.tsx index 4dd1cb6620..c2c8392222 100644 --- a/src/pages/IstioConfigNew/IstioConfigNewPage.tsx +++ b/src/pages/IstioConfigNew/IstioConfigNewPage.tsx @@ -276,6 +276,8 @@ class IstioConfigNewPage extends React.Component { onChangePeerAuthentication = (peerAuthentication: PeerAuthenticationState) => { this.setState(prevState => { prevState.peerAuthentication.workloadSelector = peerAuthentication.workloadSelector; + prevState.peerAuthentication.mtls = peerAuthentication.mtls; + prevState.peerAuthentication.portLevelMtls = peerAuthentication.portLevelMtls; return { peerAuthentication: prevState.peerAuthentication }; From df6da4b9a7accd675213dc9f2013166133466390 Mon Sep 17 00:00:00 2001 From: Lucas Ponce Date: Mon, 25 May 2020 10:26:32 +0200 Subject: [PATCH 04/12] Refactor IstioConfigNewPage for better place of validations --- src/helpers/ValidationHelpers.ts | 10 + .../AuthorizationPolicyForm.tsx | 100 ++++---- .../From/SourceBuilder.tsx | 35 ++- .../AuthorizationPolicyForm/RuleList.tsx | 5 +- .../When/ConditionBuilder.tsx | 85 ++++++- src/pages/IstioConfigNew/GatewayForm.tsx | 86 ++++--- .../IstioConfigNew/IstioConfigNewPage.tsx | 227 ++++++------------ .../IstioConfigNew/PeerAuthenticationForm.tsx | 10 +- .../RequestAuthenticationForm.tsx | 10 +- src/pages/IstioConfigNew/SidecarForm.tsx | 127 +++++----- .../ThreeScaleHandlerDetailsPage.tsx | 7 +- src/utils/IstioConfigUtils.ts | 22 +- src/utils/__tests__/IstioConfigUtils.test.ts | 37 +-- 13 files changed, 442 insertions(+), 319 deletions(-) create mode 100644 src/helpers/ValidationHelpers.ts diff --git a/src/helpers/ValidationHelpers.ts b/src/helpers/ValidationHelpers.ts new file mode 100644 index 0000000000..941065b463 --- /dev/null +++ b/src/helpers/ValidationHelpers.ts @@ -0,0 +1,10 @@ +// Kubernetes ID validation helper, used to allow mark a warning in the form edition +const k8sRegExpName = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[-a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/; +export const isValidK8SName = (name: string) => { + return name === '' ? false : name.search(k8sRegExpName) === 0; +}; + +const regExpRequestHeaders = /^request\.headers\[.*\]$/; +export const isValidRequestHeaderName = (name: string) => { + return name === '' ? false : name.search(regExpRequestHeaders) === 0; +}; diff --git a/src/pages/IstioConfigNew/AuthorizationPolicyForm.tsx b/src/pages/IstioConfigNew/AuthorizationPolicyForm.tsx index 2e368795b0..7f5e5634fb 100644 --- a/src/pages/IstioConfigNew/AuthorizationPolicyForm.tsx +++ b/src/pages/IstioConfigNew/AuthorizationPolicyForm.tsx @@ -13,23 +13,19 @@ export type AuthorizationPolicyState = { workloadSelector: string; action: string; rules: Rule[]; -}; - -type State = { // Used to identify DENY_ALL, ALLOW_ALL or RULES rulesForm: string; addWorkloadSelector: boolean; workloadSelectorValid: boolean; - workloadSelectorLabels: string; - action: string; - rules: Rule[]; }; -const DENY_ALL = 'DENY_ALL'; -const ALLOW_ALL = 'ALLOW_ALL'; -const RULES = 'RULES'; -const ALLOW = 'ALLOW'; -const DENY = 'DENY'; +export const AUTHORIZACION_POLICY = 'AuthorizationPolicy'; +export const AUTHORIZATION_POLICIES = 'authorizationpolicies'; +export const DENY_ALL = 'DENY_ALL'; +export const ALLOW_ALL = 'ALLOW_ALL'; +export const RULES = 'RULES'; +export const ALLOW = 'ALLOW'; +export const DENY = 'DENY'; const HELPER_TEXT = { DENY_ALL: 'Denies all requests to workloads in given namespace(s)', @@ -40,34 +36,38 @@ const HELPER_TEXT = { const rulesFormValues = [DENY_ALL, ALLOW_ALL, RULES]; const actions = [ALLOW, DENY]; -export const INIT_AUTHORIZATION_POLICY = (): AuthorizationPolicyState => ({ - policy: DENY_ALL, +export const initAuthorizationPolicy = (): AuthorizationPolicyState => ({ + policy: DENY, workloadSelector: '', action: ALLOW, - rules: [] + rules: [], + rulesForm: DENY_ALL, + addWorkloadSelector: false, + workloadSelectorValid: false }); -class AuthorizationPolicyForm extends React.Component { +export const isAuthorizationPolicyStateValid = (ap: AuthorizationPolicyState): boolean => { + const workloadSelectorRule = ap.addWorkloadSelector ? ap.workloadSelectorValid : true; + const denyRule = ap.action === DENY ? ap.rules.length > 0 : true; + + return workloadSelectorRule && denyRule; +}; + +class AuthorizationPolicyForm extends React.Component { constructor(props: Props) { super(props); - this.state = { - rulesForm: this.props.authorizationPolicy.policy, - addWorkloadSelector: false, - workloadSelectorValid: false, - workloadSelectorLabels: this.props.authorizationPolicy.workloadSelector, - action: this.props.authorizationPolicy.action, - rules: [] - }; + this.state = initAuthorizationPolicy(); } componentDidMount() { this.setState({ - rulesForm: this.props.authorizationPolicy.policy, - addWorkloadSelector: false, - workloadSelectorValid: false, - workloadSelectorLabels: this.props.authorizationPolicy.workloadSelector, + policy: this.props.authorizationPolicy.policy, + workloadSelector: this.props.authorizationPolicy.workloadSelector, action: this.props.authorizationPolicy.action, - rules: [] + rules: [], + rulesForm: this.props.authorizationPolicy.rulesForm, + addWorkloadSelector: this.props.authorizationPolicy.addWorkloadSelector, + workloadSelectorValid: this.props.authorizationPolicy.workloadSelectorValid }); } @@ -80,12 +80,26 @@ class AuthorizationPolicyForm extends React.Component { ); }; + onChangeWorkloadSelector = () => { + this.setState( + prevState => { + return { + addWorkloadSelector: !prevState.addWorkloadSelector + }; + }, + () => this.onAuthorizationChange() + ); + }; + addWorkloadLabels = (value: string, _) => { if (value.length === 0) { - this.setState({ - workloadSelectorValid: false, - workloadSelectorLabels: '' - }); + this.setState( + { + workloadSelectorValid: false, + workloadSelector: '' + }, + () => this.onAuthorizationChange() + ); return; } value = value.trim(); @@ -111,7 +125,7 @@ class AuthorizationPolicyForm extends React.Component { this.setState( { workloadSelectorValid: isValid, - workloadSelectorLabels: value + workloadSelector: value }, () => this.onAuthorizationChange() ); @@ -151,13 +165,7 @@ class AuthorizationPolicyForm extends React.Component { }; onAuthorizationChange = () => { - const authorizationPolicy: AuthorizationPolicyState = { - policy: this.state.rulesForm, - workloadSelector: this.state.workloadSelectorLabels, - action: this.state.action, - rules: this.state.rules - }; - this.props.onChange(authorizationPolicy); + this.props.onChange(this.state); }; render() { @@ -177,11 +185,7 @@ class AuthorizationPolicyForm extends React.Component { label={' '} labelOff={' '} isChecked={this.state.addWorkloadSelector} - onChange={() => { - this.setState(prevState => ({ - addWorkloadSelector: !prevState.addWorkloadSelector - })); - }} + onChange={this.onChangeWorkloadSelector} /> )} @@ -197,7 +201,7 @@ class AuthorizationPolicyForm extends React.Component { id="gwHosts" name="gwHosts" isDisabled={!this.state.addWorkloadSelector} - value={this.state.workloadSelectorLabels} + value={this.state.workloadSelector} onChange={this.addWorkloadLabels} isValid={this.state.workloadSelectorValid} /> @@ -213,7 +217,9 @@ class AuthorizationPolicyForm extends React.Component { )} {this.state.rulesForm === RULES && } - {this.state.rulesForm === RULES && } + {this.state.rulesForm === RULES && ( + + )} ); } diff --git a/src/pages/IstioConfigNew/AuthorizationPolicyForm/From/SourceBuilder.tsx b/src/pages/IstioConfigNew/AuthorizationPolicyForm/From/SourceBuilder.tsx index 67a0b198ea..e502254400 100644 --- a/src/pages/IstioConfigNew/AuthorizationPolicyForm/From/SourceBuilder.tsx +++ b/src/pages/IstioConfigNew/AuthorizationPolicyForm/From/SourceBuilder.tsx @@ -3,6 +3,9 @@ import { cellWidth, ICell, Table, TableBody, TableHeader } from '@patternfly/rea // Use TextInputBase like workaround while PF4 team work in https://github.com/patternfly/patternfly-react/issues/4072 import { Button, FormSelect, FormSelectOption, TextInputBase as TextInput } from '@patternfly/react-core'; import { PlusCircleIcon } from '@patternfly/react-icons'; +import { isValidIp } from '../../../../utils/IstioConfigUtils'; +import { style } from 'typestyle'; +import { PfColors } from '../../../../components/Pf/PfColors'; type Props = { onAddFrom: (source: { [key: string]: string[] }) => void; @@ -28,6 +31,10 @@ const INIT_SOURCE_FIELDS = [ 'notIpBlocks' ].sort(); +const noSourceStyle = style({ + color: PfColors.Red100 +}); + const headerCells: ICell[] = [ { title: 'Source Field', @@ -99,6 +106,20 @@ class SourceBuilder extends React.Component { ); }; + // Helper to identify when some values are valid + isValidSource = (): [boolean, string] => { + if (this.state.newSourceField === 'ipBlocks' || this.state.newSourceField === 'notIpBlocks') { + const validIp = isValidIp(this.state.newValues); + if (!validIp) { + return [false, 'Not valid IP']; + } + } + if (this.state.newValues.length === 0) { + return [false, 'Empty value']; + } + return [true, '']; + }; + // @ts-ignore actionResolver = (rowData, { rowIndex }) => { const removeAction = { @@ -127,6 +148,7 @@ class SourceBuilder extends React.Component { }; rows = () => { + const [isValidSource, invalidText] = this.isValidSource(); return Object.keys(this.state.source) .map((sourceField, i) => { return { @@ -159,11 +181,22 @@ class SourceBuilder extends React.Component { aria-describedby="add new source values" name="addNewValues" onChange={this.onAddNewValues} + isValid={isValidSource} /> + {!isValidSource && ( +
+ {invalidText} +
+ )} , <> {this.state.sourceFields.length > 0 && ( - - - ] - } + , + ], + }, ]); } diff --git a/src/pages/IstioConfigNew/IstioConfigNewPage.tsx b/src/pages/IstioConfigNew/IstioConfigNewPage.tsx index 18f7e68f52..2fcb49c266 100644 --- a/src/pages/IstioConfigNew/IstioConfigNewPage.tsx +++ b/src/pages/IstioConfigNew/IstioConfigNewPage.tsx @@ -19,7 +19,7 @@ import { buildGateway, buildPeerAuthentication, buildRequestAuthentication, - buildSidecar + buildSidecar, } from '../../components/IstioWizards/IstioWizardActions'; import { MessageType } from '../../types/MessageCenter'; import AuthorizationPolicyForm, { @@ -27,21 +27,21 @@ import AuthorizationPolicyForm, { AUTHORIZATION_POLICIES, AuthorizationPolicyState, initAuthorizationPolicy, - isAuthorizationPolicyStateValid + isAuthorizationPolicyStateValid, } from './AuthorizationPolicyForm'; import PeerAuthenticationForm, { initPeerAuthentication, isPeerAuthenticationStateValid, PEER_AUTHENTICATION, PEER_AUTHENTICATIONS, - PeerAuthenticationState + PeerAuthenticationState, } from './PeerAuthenticationForm'; import RequestAuthenticationForm, { initRequestAuthentication, isRequestAuthenticationStateValid, REQUEST_AUTHENTICATION, REQUEST_AUTHENTICATIONS, - RequestAuthenticationState + RequestAuthenticationState, } from './RequestAuthenticationForm'; import { isValidK8SName } from '../../helpers/ValidationHelpers'; @@ -67,7 +67,7 @@ const DIC = { Gateway: GATEWAYS, PeerAuthentication: PEER_AUTHENTICATIONS, RequestAuthentication: REQUEST_AUTHENTICATIONS, - Sidecar: SIDECARS + Sidecar: SIDECARS, }; const istioResourceOptions = [ @@ -75,7 +75,7 @@ const istioResourceOptions = [ { value: GATEWAY, label: GATEWAY, disabled: false }, { value: PEER_AUTHENTICATION, label: PEER_AUTHENTICATION, disabled: false }, { value: REQUEST_AUTHENTICATION, label: REQUEST_AUTHENTICATION, disabled: false }, - { value: SIDECAR, label: SIDECAR, disabled: false } + { value: SIDECAR, label: SIDECAR, disabled: false }, ]; const initState = (): State => ({ @@ -87,7 +87,7 @@ const initState = (): State => ({ peerAuthentication: initPeerAuthentication(), requestAuthentication: initRequestAuthentication(), // Init with the istio-system/* for sidecar - sidecar: initSidecar(serverConfig.istioNamespace + '/*') + sidecar: initSidecar(serverConfig.istioNamespace + '/*'), }); class IstioConfigNewPage extends React.Component { @@ -125,14 +125,14 @@ class IstioConfigNewPage extends React.Component { fetchPermissions = () => { if (this.props.activeNamespaces.length > 0) { this.promises - .register('permissions', API.getIstioPermissions(this.props.activeNamespaces.map(n => n.name))) - .then(permResponse => { + .register('permissions', API.getIstioPermissions(this.props.activeNamespaces.map((n) => n.name))) + .then((permResponse) => { this.setState( { - istioPermissions: permResponse.data + istioPermissions: permResponse.data, }, () => { - this.props.activeNamespaces.forEach(ns => { + this.props.activeNamespaces.forEach((ns) => { if (!this.canCreate(ns.name)) { AlertUtils.addWarning('User has not permissions to create Istio Config on namespace: ' + ns.name); } @@ -140,7 +140,7 @@ class IstioConfigNewPage extends React.Component { } ); }) - .catch(error => { + .catch((error) => { // Canceled errors are expected on this query when page is unmounted if (!error.isCanceled) { AlertUtils.addError('Could not fetch Permissions.', error); @@ -152,48 +152,50 @@ class IstioConfigNewPage extends React.Component { onIstioResourceChange = (value, _) => { this.setState({ istioResource: value, - name: '' + name: '', }); }; onNameChange = (value, _) => { this.setState({ - name: value + name: value, }); }; onIstioResourceCreate = () => { const jsonIstioObjects: { namespace: string; json: string }[] = []; - this.props.activeNamespaces.forEach(ns => { + this.props.activeNamespaces.forEach((ns) => { switch (this.state.istioResource) { case AUTHORIZACION_POLICY: jsonIstioObjects.push({ namespace: ns.name, - json: JSON.stringify(buildAuthorizationPolicy(this.state.name, ns.name, this.state.authorizationPolicy)) + json: JSON.stringify(buildAuthorizationPolicy(this.state.name, ns.name, this.state.authorizationPolicy)), }); break; case GATEWAY: jsonIstioObjects.push({ namespace: ns.name, - json: JSON.stringify(buildGateway(this.state.name, ns.name, this.state.gateway)) + json: JSON.stringify(buildGateway(this.state.name, ns.name, this.state.gateway)), }); break; case PEER_AUTHENTICATION: jsonIstioObjects.push({ namespace: ns.name, - json: JSON.stringify(buildPeerAuthentication(this.state.name, ns.name, this.state.peerAuthentication)) + json: JSON.stringify(buildPeerAuthentication(this.state.name, ns.name, this.state.peerAuthentication)), }); break; case REQUEST_AUTHENTICATION: jsonIstioObjects.push({ namespace: ns.name, - json: JSON.stringify(buildRequestAuthentication(this.state.name, ns.name, this.state.requestAuthentication)) + json: JSON.stringify( + buildRequestAuthentication(this.state.name, ns.name, this.state.requestAuthentication) + ), }); break; case SIDECAR: jsonIstioObjects.push({ namespace: ns.name, - json: JSON.stringify(buildSidecar(this.state.name, ns.name, this.state.sidecar)) + json: JSON.stringify(buildSidecar(this.state.name, ns.name, this.state.sidecar)), }); break; } @@ -202,15 +204,15 @@ class IstioConfigNewPage extends React.Component { this.promises .registerAll( 'Create ' + DIC[this.state.istioResource], - jsonIstioObjects.map(o => API.createIstioConfigDetail(o.namespace, DIC[this.state.istioResource], o.json)) + jsonIstioObjects.map((o) => API.createIstioConfigDetail(o.namespace, DIC[this.state.istioResource], o.json)) ) - .then(results => { + .then((results) => { if (results.length > 0) { AlertUtils.add('Istio ' + this.state.istioResource + ' created', 'default', MessageType.SUCCESS); } this.backToList(); }) - .catch(error => { + .catch((error) => { AlertUtils.addError('Could not create Istio ' + this.state.istioResource + ' objects.', error); }); }; @@ -241,58 +243,58 @@ class IstioConfigNewPage extends React.Component { }; onChangeAuthorizationPolicy = (authorizationPolicy: AuthorizationPolicyState) => { - this.setState(prevState => { + this.setState((prevState) => { Object.keys(prevState.authorizationPolicy).forEach( - key => (prevState.authorizationPolicy[key] = authorizationPolicy[key]) + (key) => (prevState.authorizationPolicy[key] = authorizationPolicy[key]) ); return { - authorizationPolicy: prevState.authorizationPolicy + authorizationPolicy: prevState.authorizationPolicy, }; }); }; onChangeGateway = (gateway: GatewayState) => { - this.setState(prevState => { - Object.keys(prevState.gateway).forEach(key => (prevState.gateway[key] = gateway[key])); + this.setState((prevState) => { + Object.keys(prevState.gateway).forEach((key) => (prevState.gateway[key] = gateway[key])); return { - gateway: prevState.gateway + gateway: prevState.gateway, }; }); }; onChangePeerAuthentication = (peerAuthentication: PeerAuthenticationState) => { - this.setState(prevState => { + this.setState((prevState) => { Object.keys(prevState.peerAuthentication).forEach( - key => (prevState.peerAuthentication[key] = peerAuthentication[key]) + (key) => (prevState.peerAuthentication[key] = peerAuthentication[key]) ); return { - peerAuthentication: prevState.peerAuthentication + peerAuthentication: prevState.peerAuthentication, }; }); }; onChangeRequestAuthentication = (requestAuthentication: RequestAuthenticationState) => { - this.setState(prevState => { + this.setState((prevState) => { Object.keys(prevState.requestAuthentication).forEach( - key => (prevState.requestAuthentication[key] = requestAuthentication[key]) + (key) => (prevState.requestAuthentication[key] = requestAuthentication[key]) ); return { - requestAuthentication: prevState.requestAuthentication + requestAuthentication: prevState.requestAuthentication, }; }); }; onChangeSidecar = (sidecar: SidecarState) => { - this.setState(prevState => { - Object.keys(prevState.sidecar).forEach(key => (prevState.sidecar[key] = sidecar[key])); + this.setState((prevState) => { + Object.keys(prevState.sidecar).forEach((key) => (prevState.sidecar[key] = sidecar[key])); return { - sidecar: prevState.sidecar + sidecar: prevState.sidecar, }; }); }; render() { - const canCreate = this.props.activeNamespaces.every(ns => this.canCreate(ns.name)); + const canCreate = this.props.activeNamespaces.every((ns) => this.canCreate(ns.name)); const isNameValid = isValidK8SName(this.state.name); const isNamespacesValid = this.props.activeNamespaces.length > 0; const isFormValid = canCreate && isNameValid && isNamespacesValid && this.isIstioFormValid(); @@ -308,7 +310,7 @@ class IstioConfigNewPage extends React.Component { isValid={isNamespacesValid} > n.name).join(',')} + value={this.props.activeNamespaces.map((n) => n.name).join(',')} isRequired={true} type="text" id="namespaces" @@ -389,13 +391,10 @@ class IstioConfigNewPage extends React.Component { const mapStateToProps = (state: KialiAppState) => { return { - activeNamespaces: activeNamespacesSelector(state) + activeNamespaces: activeNamespacesSelector(state), }; }; -const IstioConfigNewPageContainer = connect( - mapStateToProps, - null -)(IstioConfigNewPage); +const IstioConfigNewPageContainer = connect(mapStateToProps, null)(IstioConfigNewPage); export default IstioConfigNewPageContainer; diff --git a/src/pages/IstioConfigNew/PeerAuthenticationForm.tsx b/src/pages/IstioConfigNew/PeerAuthenticationForm.tsx index 89f050fe40..093a7a4742 100644 --- a/src/pages/IstioConfigNew/PeerAuthenticationForm.tsx +++ b/src/pages/IstioConfigNew/PeerAuthenticationForm.tsx @@ -8,24 +8,24 @@ import { PfColors } from '../../components/Pf/PfColors'; const noPortMtlsStyle = style({ marginTop: 15, - color: PfColors.Red100 + color: PfColors.Red100, }); const headerCells: ICell[] = [ { title: 'Port Number', transforms: [cellWidth(20) as any], - props: {} + props: {}, }, { title: 'Mutual TLS Mode', transforms: [cellWidth(20) as any], - props: {} + props: {}, }, { title: '', - props: {} - } + props: {}, + }, ]; type Props = { @@ -60,8 +60,8 @@ export const initPeerAuthentication = (): PeerAuthenticationState => ({ addPortMtls: false, addNewPortMtls: { port: '', - mtls: PeerAuthenticationMutualTLSMode.UNSET - } + mtls: PeerAuthenticationMutualTLSMode.UNSET, + }, }); export const isPeerAuthenticationStateValid = (pa: PeerAuthenticationState): boolean => { @@ -83,8 +83,8 @@ class PeerAuthenticationForm extends React.Component { this.setState( - prevState => { + (prevState) => { return { - addWorkloadSelector: !prevState.addWorkloadSelector + addWorkloadSelector: !prevState.addWorkloadSelector, }; }, () => this.onPeerAuthenticationChange() @@ -113,9 +113,9 @@ class PeerAuthenticationForm extends React.Component { this.setState( - prevState => { + (prevState) => { return { - addPortMtls: !prevState.addPortMtls + addPortMtls: !prevState.addPortMtls, }; }, () => this.onPeerAuthenticationChange() @@ -127,7 +127,7 @@ class PeerAuthenticationForm extends React.Component this.onPeerAuthenticationChange() ); @@ -156,7 +156,7 @@ class PeerAuthenticationForm extends React.Component this.onPeerAuthenticationChange() ); @@ -169,7 +169,7 @@ class PeerAuthenticationForm extends React.Component { this.setState( { - mtls: value + mtls: value, }, () => this.onPeerAuthenticationChange() ); @@ -177,12 +177,12 @@ class PeerAuthenticationForm extends React.Component { this.setState( - prevState => { + (prevState) => { return { addNewPortMtls: { port: value.trim(), - mtls: prevState.addNewPortMtls.mtls - } + mtls: prevState.addNewPortMtls.mtls, + }, }; }, () => this.onPeerAuthenticationChange() @@ -191,12 +191,12 @@ class PeerAuthenticationForm extends React.Component { this.setState( - prevState => { + (prevState) => { return { addNewPortMtls: { port: prevState.addNewPortMtls.port, - mtls: value - } + mtls: value, + }, }; }, () => this.onPeerAuthenticationChange() @@ -205,14 +205,14 @@ class PeerAuthenticationForm extends React.Component { this.setState( - prevState => { + (prevState) => { prevState.portLevelMtls.push(prevState.addNewPortMtls); return { portLevelMtls: prevState.portLevelMtls, addNewPortMtls: { port: '', - mtls: PeerAuthenticationMutualTLSMode.UNSET - } + mtls: PeerAuthenticationMutualTLSMode.UNSET, + }, }; }, () => this.onPeerAuthenticationChange() @@ -226,15 +226,15 @@ class PeerAuthenticationForm extends React.Component { this.setState( - prevState => { + (prevState) => { prevState.portLevelMtls.splice(rowIndex, 1); return { - portLevelMtls: prevState.portLevelMtls + portLevelMtls: prevState.portLevelMtls, }; }, () => this.onPeerAuthenticationChange() ); - } + }, }; if (rowIndex < this.props.peerAuthentication.portLevelMtls.length) { return [removeAction]; @@ -246,7 +246,7 @@ class PeerAuthenticationForm extends React.Component ({ key: 'portMtls' + i, - cells: [<>{pmtls.port}, <>{pmtls.mtls}, ''] + cells: [<>{pmtls.port}, <>{pmtls.mtls}, ''], })) .concat([ { @@ -285,9 +285,9 @@ class PeerAuthenticationForm extends React.Component Add Port MTLS - - ] - } + , + ], + }, ]); } diff --git a/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx b/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx index 6d70598826..f4165ba75d 100644 --- a/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx +++ b/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx @@ -26,7 +26,7 @@ export const initRequestAuthentication = (): RequestAuthenticationState => ({ jwtRules: [], addWorkloadSelector: false, workloadSelectorValid: false, - addJWTRules: false + addJWTRules: false, }); export const isRequestAuthenticationStateValid = (ra: RequestAuthenticationState): boolean => { @@ -48,7 +48,7 @@ class RequestAuthenticationForm extends React.Component { this.setState( - prevState => { + (prevState) => { return { - addWorkloadSelector: !prevState.addWorkloadSelector + addWorkloadSelector: !prevState.addWorkloadSelector, }; }, () => this.onRequestAuthenticationChange() @@ -69,9 +69,9 @@ class RequestAuthenticationForm extends React.Component { this.setState( - prevState => { + (prevState) => { return { - addJWTRules: !prevState.addJWTRules + addJWTRules: !prevState.addJWTRules, }; }, () => this.onRequestAuthenticationChange() @@ -83,7 +83,7 @@ class RequestAuthenticationForm extends React.Component this.onRequestAuthenticationChange() ); @@ -112,7 +112,7 @@ class RequestAuthenticationForm extends React.Component this.onRequestAuthenticationChange() ); @@ -120,10 +120,10 @@ class RequestAuthenticationForm extends React.Component { this.setState( - prevState => { + (prevState) => { prevState.jwtRules.push(jwtRule); return { - jwtRules: prevState.jwtRules + jwtRules: prevState.jwtRules, }; }, () => this.onRequestAuthenticationChange() @@ -132,10 +132,10 @@ class RequestAuthenticationForm extends React.Component { this.setState( - prevState => { + (prevState) => { prevState.jwtRules.splice(index, 1); return { - jwtRules: prevState.jwtRules + jwtRules: prevState.jwtRules, }; }, () => this.onRequestAuthenticationChange() diff --git a/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx b/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx index 7f4113414f..fcd9d25e2a 100644 --- a/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx +++ b/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx @@ -24,24 +24,24 @@ const INIT_JWT_RULE_FIELDS = [ 'fromHeaders', 'fromParams', 'outputPayloadToHeader', - 'forwardOriginalToken' + 'forwardOriginalToken', ].sort(); const headerCells: ICell[] = [ { title: 'JWT Rule Field', transforms: [cellWidth(30) as any], - props: {} + props: {}, }, { title: 'Values', transforms: [cellWidth(70) as any], - props: {} + props: {}, }, { title: '', - props: {} - } + props: {}, + }, ]; export const formatJwtField = (jwtField: string, jwtRule: JWTRule): string => { @@ -55,7 +55,7 @@ export const formatJwtField = (jwtField: string, jwtRule: JWTRule): string => { case 'fromHeaders': return jwtRule.fromHeaders ? jwtRule.fromHeaders - .map(header => { + .map((header) => { if (header.prefix) { return header.name + ': ' + header.prefix; } else { @@ -82,24 +82,24 @@ class JwtRuleBuilder extends React.Component { jwtRuleFields: Object.assign([], INIT_JWT_RULE_FIELDS), jwtRule: {}, newJwtField: 'issuer', - newValues: '' + newValues: '', }; } onAddJwtField = (value: string, _) => { this.setState({ - newJwtField: value + newJwtField: value, }); }; onAddNewValues = (value: string, _) => { this.setState({ - newValues: value + newValues: value, }); }; onUpdateJwtRule = () => { - this.setState(prevState => { + this.setState((prevState) => { const i = prevState.jwtRuleFields.indexOf(prevState.newJwtField); if (i > -1) { prevState.jwtRuleFields.splice(i, 1); @@ -119,10 +119,10 @@ class JwtRuleBuilder extends React.Component { // "Authorization: Bearer , Authorization: Bearer, Security " // In [{name: 'Authorization', prefix: 'Bearer '}, {name: 'Authorization', prefix: 'Bearer'}, {name: 'Security}] prevState.jwtRule.fromHeaders = []; - prevState.newValues.split(',').forEach(value => { + prevState.newValues.split(',').forEach((value) => { const values = value.split(':'); const header: JWTHeader = { - name: values[0] + name: values[0], }; if (values.length > 1) { header.prefix = values[1].trimLeft(); @@ -149,7 +149,7 @@ class JwtRuleBuilder extends React.Component { jwtRuleFields: prevState.jwtRuleFields, jwtRule: prevState.jwtRule, newJwtField: prevState.jwtRuleFields[0], - newValues: '' + newValues: '', }; }); }; @@ -161,7 +161,7 @@ class JwtRuleBuilder extends React.Component { jwtRuleFields: Object.assign([], INIT_JWT_RULE_FIELDS), jwtRule: {}, newJwtField: INIT_JWT_RULE_FIELDS[0], - newValues: '' + newValues: '', }, () => this.props.onAddJwtRule(oldJwtRule) ); @@ -175,7 +175,7 @@ class JwtRuleBuilder extends React.Component { onClick: (event, rowIndex, rowData, extraData) => { // Fetch sourceField from rowData, it's a fixed string on children const removeJwtRuleField = rowData.cells[0].props.children.toString(); - this.setState(prevState => { + this.setState((prevState) => { prevState.jwtRuleFields.push(removeJwtRuleField); delete prevState.jwtRule[removeJwtRuleField]; const newJwtRuleFields = prevState.jwtRuleFields.sort(); @@ -183,10 +183,10 @@ class JwtRuleBuilder extends React.Component { jwtRuleFields: newJwtRuleFields, jwtRule: prevState.jwtRule, newJwtField: newJwtRuleFields[0], - newValues: '' + newValues: '', }; }); - } + }, }; if (rowIndex < Object.keys(this.state.jwtRule).length) { return [removeAction]; @@ -203,7 +203,7 @@ class JwtRuleBuilder extends React.Component { .map((jwtField, i) => { return { key: 'jwtField' + i, - cells: [<>{jwtField}, <>{formatJwtField(jwtField, this.state.jwtRule)}, <>] + cells: [<>{jwtField}, <>{formatJwtField(jwtField, this.state.jwtRule)}, <>], }; }) .concat([ @@ -237,9 +237,9 @@ class JwtRuleBuilder extends React.Component { {this.state.jwtRuleFields.length > 0 && ( - - ] - } + , + ], + }, ]); } @@ -225,8 +225,8 @@ class SidecarForm extends React.Component { isChecked={this.state.addWorkloadSelector} onChange={() => { this.setState( - prevState => ({ - addWorkloadSelector: !prevState.addWorkloadSelector + (prevState) => ({ + addWorkloadSelector: !prevState.addWorkloadSelector, }), () => this.props.onChange(this.state) ); diff --git a/src/pages/extensions/threescale/ThreeScaleHandlerDetails/ThreeScaleHandlerDetailsPage.tsx b/src/pages/extensions/threescale/ThreeScaleHandlerDetails/ThreeScaleHandlerDetailsPage.tsx index 1e1b54d100..34ce7b41ea 100644 --- a/src/pages/extensions/threescale/ThreeScaleHandlerDetails/ThreeScaleHandlerDetailsPage.tsx +++ b/src/pages/extensions/threescale/ThreeScaleHandlerDetails/ThreeScaleHandlerDetailsPage.tsx @@ -18,7 +18,7 @@ import { TextVariants, Title, Toolbar, - ToolbarSection + ToolbarSection, } from '@patternfly/react-core'; import { style } from 'typestyle'; import { PfColors } from '../../../../components/Pf/PfColors'; @@ -54,10 +54,10 @@ interface State { // i.e. no namespaces controllers, then some styles need to be adjusted manually const extensionHeader = style({ padding: '0px 20px 18px 20px', - backgroundColor: PfColors.White + backgroundColor: PfColors.White, }); const breadcrumbPadding = style({ - padding: '22px 0 5px 0' + padding: '22px 0 5px 0', }); const containerPadding = style({ padding: '20px 20px 20px 20px' }); // Toolbar in 3scale details page is added manually. @@ -67,7 +67,7 @@ const rightToolbarStyle = style({ right: '20px', zIndex: 1, marginTop: '-30px', - backgroundColor: PfColors.White + backgroundColor: PfColors.White, }); class ThreeScaleHandlerDetailsPage extends React.Component, State> { @@ -81,17 +81,17 @@ class ThreeScaleHandlerDetailsPage extends React.Component { API.getThreeScaleInfo() - .then(result => { + .then((result) => { const threeScaleInfo = result.data; if (threeScaleInfo.enabled) { if (handlerName) { API.getThreeScaleHandlers() - .then(results => { + .then((results) => { let handler: ThreeScaleHandler | undefined = undefined; for (let i = 0; results.data.length; i++) { if (results.data[i].name === handlerName) { @@ -116,25 +116,25 @@ class ThreeScaleHandlerDetailsPage extends React.Component { + .catch((error) => { AlertUtils.addError('Could not fetch ThreeScaleHandlers.', error); }); } else { this.setState({ - threeScaleInfo: threeScaleInfo + threeScaleInfo: threeScaleInfo, }); } } else { AlertUtils.addError('Kiali has 3scale extension enabled but 3scale adapter is not detected in the cluster'); } }) - .catch(error => { + .catch((error) => { AlertUtils.addError('Could not fetch ThreeScaleInfo.', error); }); }; @@ -163,7 +163,7 @@ class ThreeScaleHandlerDetailsPage extends React.Component this.setState({ deleteModalOpen: true })}> Delete - + , ]} /> , + , ]} > @@ -240,7 +240,7 @@ class ThreeScaleHandlerDetailsPage extends React.Component { - this.setState(prevState => { + this.setState((prevState) => { const newThreeScaleHandler = prevState.handler; switch (field) { case 'handlerName': @@ -260,7 +260,7 @@ class ThreeScaleHandlerDetailsPage extends React.Component { if (this.state.isNew) { API.createThreeScaleHandler(JSON.stringify(this.state.handler)) - .then(_ => this.goHandlersPage()) - .catch(error => AlertUtils.addError('Could not create ThreeScaleHandlers.', error)); + .then((_) => this.goHandlersPage()) + .catch((error) => AlertUtils.addError('Could not create ThreeScaleHandlers.', error)); } else { API.updateThreeScaleHandler(this.state.handler.name, JSON.stringify(this.state.handler)) - .then(_ => this.goHandlersPage()) - .catch(error => AlertUtils.addError('Could not update ThreeScaleHandlers.', error)); + .then((_) => this.goHandlersPage()) + .catch((error) => AlertUtils.addError('Could not update ThreeScaleHandlers.', error)); } }; // It invokes backend to delete a 3scale handler deleteHandler = () => { API.deleteThreeScaleHandler(this.state.handler.name) - .then(_ => this.goHandlersPage()) - .catch(error => AlertUtils.addError('Could not delete ThreeScaleHandlers.', error)); + .then((_) => this.goHandlersPage()) + .catch((error) => AlertUtils.addError('Could not delete ThreeScaleHandlers.', error)); }; render() { @@ -329,7 +329,7 @@ class ThreeScaleHandlerDetailsPage extends React.Component this.changeHandler('handlerName', value)} + onChange={(value) => this.changeHandler('handlerName', value)} isDisabled={!this.state.isNew} /> @@ -343,7 +343,7 @@ class ThreeScaleHandlerDetailsPage extends React.Component this.changeHandler('serviceId', value)} + onChange={(value) => this.changeHandler('serviceId', value)} /> this.changeHandler('systemUrl', value)} + onChange={(value) => this.changeHandler('systemUrl', value)} /> this.changeHandler('accessToken', value)} + onChange={(value) => this.changeHandler('accessToken', value)} /> diff --git a/src/types/IstioObjects.ts b/src/types/IstioObjects.ts index 0488685d39..8f9affad23 100644 --- a/src/types/IstioObjects.ts +++ b/src/types/IstioObjects.ts @@ -50,7 +50,7 @@ export type Validations = { [key1: string]: { [key2: string]: ObjectValidation } export enum ValidationTypes { Error = 'error', Warning = 'warning', - Correct = 'correct' + Correct = 'correct', } export interface ObjectValidation { @@ -495,7 +495,7 @@ export interface Gateway extends IstioObject { export enum CaptureMode { DEFAULT = 'DEFAULT', IPTABLES = 'IPTABLES', - NONE = 'NONE' + NONE = 'NONE', } // 1.6 @@ -668,7 +668,7 @@ export interface TargetSelector { export enum MutualTlsMode { STRICT = 'STRICT', - PERMISSIVE = 'PERMISSIVE' + PERMISSIVE = 'PERMISSIVE', } export interface MutualTls { @@ -694,7 +694,7 @@ export interface OriginAuthenticationMethod { export enum PrincipalBinding { USE_PEER = 'USE_PEER', - USE_ORIGIN = 'USE_ORIGIN' + USE_ORIGIN = 'USE_ORIGIN', } export interface PolicySpec { @@ -856,7 +856,7 @@ export enum PeerAuthenticationMutualTLSMode { UNSET = 'UNSET', DISABLE = 'DISABLE', PERMISSIVE = 'PERMISSIVE', - STRICT = 'STRICT' + STRICT = 'STRICT', } // 1.6 diff --git a/src/utils/__tests__/IstioConfigUtils.test.ts b/src/utils/__tests__/IstioConfigUtils.test.ts index af4183a3dd..51090919e7 100644 --- a/src/utils/__tests__/IstioConfigUtils.test.ts +++ b/src/utils/__tests__/IstioConfigUtils.test.ts @@ -4,23 +4,23 @@ describe('Validate JSON Patchs', () => { const gateway: object = { kind: 'Gateway', namespace: { - name: 'bookinfo' + name: 'bookinfo', }, spec: { selector: { - istio: 'ingressgateway' + istio: 'ingressgateway', }, servers: [ { port: { number: 80, name: 'http', - protocol: 'HTTP' + protocol: 'HTTP', }, - hosts: ['*'] - } - ] - } + hosts: ['*'], + }, + ], + }, }; const gatewayModified: object = { @@ -28,19 +28,19 @@ describe('Validate JSON Patchs', () => { kind: 'Gateway', spec: { selector: { - app: 'myapp' + app: 'myapp', }, servers: [ { port: { number: 80, name: 'http', - protocol: 'HTTP' + protocol: 'HTTP', }, - hosts: ['*'] - } - ] - } + hosts: ['*'], + }, + ], + }, }; it('Basic Test', () => { From 68d24b10ac56982d6b0f2c16373054391a2f9cb4 Mon Sep 17 00:00:00 2001 From: Lucas Ponce Date: Mon, 25 May 2020 15:03:54 +0200 Subject: [PATCH 09/12] Fix ci errors --- src/pages/IstioConfigNew/IstioConfigNewPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/IstioConfigNew/IstioConfigNewPage.tsx b/src/pages/IstioConfigNew/IstioConfigNewPage.tsx index 2fcb49c266..0279996c05 100644 --- a/src/pages/IstioConfigNew/IstioConfigNewPage.tsx +++ b/src/pages/IstioConfigNew/IstioConfigNewPage.tsx @@ -239,7 +239,6 @@ class IstioConfigNewPage extends React.Component { default: return false; } - return false; }; onChangeAuthorizationPolicy = (authorizationPolicy: AuthorizationPolicyState) => { From ef9b6db86d33a606222b6bcb82f5a2fe3c3ec958 Mon Sep 17 00:00:00 2001 From: Lucas Ponce Date: Mon, 25 May 2020 17:41:35 +0200 Subject: [PATCH 10/12] Address Hayk and Xavi's comments --- .../From/SourceBuilder.tsx | 24 ++++--- .../To/OperationBuilder.tsx | 52 +++++++-------- .../JwtRuleBuilder.tsx | 63 ++++++++++++++++--- 3 files changed, 95 insertions(+), 44 deletions(-) diff --git a/src/pages/IstioConfigNew/AuthorizationPolicyForm/From/SourceBuilder.tsx b/src/pages/IstioConfigNew/AuthorizationPolicyForm/From/SourceBuilder.tsx index 4cb9f4d136..79df323830 100644 --- a/src/pages/IstioConfigNew/AuthorizationPolicyForm/From/SourceBuilder.tsx +++ b/src/pages/IstioConfigNew/AuthorizationPolicyForm/From/SourceBuilder.tsx @@ -109,12 +109,13 @@ class SourceBuilder extends React.Component { // Helper to identify when some values are valid isValidSource = (): [boolean, string] => { if (this.state.newSourceField === 'ipBlocks' || this.state.newSourceField === 'notIpBlocks') { - const validIp = isValidIp(this.state.newValues); + const validIp = this.state.newValues.split(',').every((ip) => isValidIp(ip)); if (!validIp) { return [false, 'Not valid IP']; } } - if (this.state.newValues.length === 0) { + const emptyValues = this.state.newValues.split(',').every((v) => v.length === 0); + if (emptyValues) { return [false, 'Empty value']; } return [true, '']; @@ -149,14 +150,15 @@ class SourceBuilder extends React.Component { rows = () => { const [isValidSource, invalidText] = this.isValidSource(); - return Object.keys(this.state.source) - .map((sourceField, i) => { - return { - key: 'sourceKey' + i, - cells: [<>{sourceField}, <>{this.state.source[sourceField].join(',')}, <>], - }; - }) - .concat([ + + const sourceRows = Object.keys(this.state.source).map((sourceField, i) => { + return { + key: 'sourceKey' + i, + cells: [<>{sourceField}, <>{this.state.source[sourceField].join(',')}, <>], + }; + }); + if (this.state.sourceFields.length > 0) { + return sourceRows.concat([ { key: 'sourceKeyNew', cells: [ @@ -202,6 +204,8 @@ class SourceBuilder extends React.Component { ], }, ]); + } + return sourceRows; }; render() { diff --git a/src/pages/IstioConfigNew/AuthorizationPolicyForm/To/OperationBuilder.tsx b/src/pages/IstioConfigNew/AuthorizationPolicyForm/To/OperationBuilder.tsx index fd7bead2ed..783b35c838 100644 --- a/src/pages/IstioConfigNew/AuthorizationPolicyForm/To/OperationBuilder.tsx +++ b/src/pages/IstioConfigNew/AuthorizationPolicyForm/To/OperationBuilder.tsx @@ -25,24 +25,24 @@ const INIT_OPERATION_FIELDS = [ 'methods', 'notMethods', 'paths', - 'notPaths' + 'notPaths', ].sort(); const headerCells: ICell[] = [ { title: 'Operation Field', transforms: [cellWidth(20) as any], - props: {} + props: {}, }, { title: 'Values', transforms: [cellWidth(80) as any], - props: {} + props: {}, }, { title: '', - props: {} - } + props: {}, + }, ]; class OperationBuilder extends React.Component { @@ -52,24 +52,24 @@ class OperationBuilder extends React.Component { operationFields: Object.assign([], INIT_OPERATION_FIELDS), operation: {}, newOperationField: INIT_OPERATION_FIELDS[0], - newValues: '' + newValues: '', }; } onAddNewOperationField = (value: string, _) => { this.setState({ - newOperationField: value + newOperationField: value, }); }; onAddNewValues = (value: string, _) => { this.setState({ - newValues: value + newValues: value, }); }; onAddOperation = () => { - this.setState(prevState => { + this.setState((prevState) => { const i = prevState.operationFields.indexOf(prevState.newOperationField); if (i > -1) { prevState.operationFields.splice(i, 1); @@ -79,7 +79,7 @@ class OperationBuilder extends React.Component { operationFields: prevState.operationFields, operation: prevState.operation, newOperationField: prevState.operationFields[0], - newValues: '' + newValues: '', }; }); }; @@ -91,7 +91,7 @@ class OperationBuilder extends React.Component { operationFields: Object.assign([], INIT_OPERATION_FIELDS), operation: {}, newOperationField: INIT_OPERATION_FIELDS[0], - newValues: '' + newValues: '', }, () => { this.props.onAddTo(toItem); @@ -107,7 +107,7 @@ class OperationBuilder extends React.Component { onClick: (event, rowIndex, rowData, extraData) => { // Fetch sourceField from rowData, it's a fixed string on children const removeOperationField = rowData.cells[0].props.children.toString(); - this.setState(prevState => { + this.setState((prevState) => { prevState.operationFields.push(removeOperationField); delete prevState.operation[removeOperationField]; const newOperationFields = prevState.operationFields.sort(); @@ -115,10 +115,10 @@ class OperationBuilder extends React.Component { operationFields: newOperationFields, operation: prevState.operation, newOperationField: newOperationFields[0], - newValues: '' + newValues: '', }; }); - } + }, }; if (rowIndex < Object.keys(this.state.operation).length) { return [removeAction]; @@ -127,14 +127,14 @@ class OperationBuilder extends React.Component { }; rows = () => { - return Object.keys(this.state.operation) - .map((operationField, i) => { - return { - key: 'operationKey' + i, - cells: [<>{operationField}, <>{this.state.operation[operationField].join(',')}, <>] - }; - }) - .concat([ + const operatorRows = Object.keys(this.state.operation).map((operationField, i) => { + return { + key: 'operationKey' + i, + cells: [<>{operationField}, <>{this.state.operation[operationField].join(',')}, <>], + }; + }); + if (this.state.operationFields.length > 0) { + return operatorRows.concat([ { key: 'operationKeyNew', cells: [ @@ -165,10 +165,12 @@ class OperationBuilder extends React.Component { {this.state.operationFields.length > 0 && ( ); From c1c82a5691905174e15638dfe987037589a82597 Mon Sep 17 00:00:00 2001 From: Lucas Ponce Date: Tue, 26 May 2020 09:43:52 +0200 Subject: [PATCH 11/12] Fix jwks and formatOriginalToken issues --- .../IstioConfigNew/RequestAuthenticationForm.tsx | 2 ++ .../RequestAuthorizationForm/JwtRuleBuilder.tsx | 6 +++++- .../RequestAuthorizationForm/JwtRuleList.tsx | 7 ++++++- src/utils/IstioConfigUtils.ts | 9 +++++++++ src/utils/__tests__/IstioConfigUtils.test.ts | 14 +++++++++++++- 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx b/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx index f4165ba75d..c850b05074 100644 --- a/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx +++ b/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx @@ -119,6 +119,8 @@ class RequestAuthenticationForm extends React.Component { + console.log('TODELETE onAddJwtRule'); + console.log(jwtRule); this.setState( (prevState) => { prevState.jwtRules.push(jwtRule); diff --git a/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx b/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx index 32744a76c1..66ea748413 100644 --- a/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx +++ b/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleBuilder.tsx @@ -6,6 +6,7 @@ import { PlusCircleIcon } from '@patternfly/react-icons'; import { TextInputBase as TextInput } from '@patternfly/react-core/dist/js/components/TextInput/TextInput'; import { style } from 'typestyle'; import { PfColors } from '../../../components/Pf/PfColors'; +import { isValidUrl } from '../../../utils/IstioConfigUtils'; type Props = { onAddJwtRule: (rule: JWTRule) => void; @@ -83,7 +84,7 @@ export const formatJwtField = (jwtField: string, jwtRule: JWTRule): string => { case 'outputPayloadToHeader': return jwtRule.outputPayloadToHeader ? jwtRule.outputPayloadToHeader : ''; case 'forwardOriginalToken': - return jwtRule.forwardOriginalToken ? '' + jwtRule.forwardOriginalToken : ''; + return jwtRule.forwardOriginalToken ? '' + jwtRule.forwardOriginalToken : 'false'; default: } return ''; @@ -216,6 +217,9 @@ class JwtRuleBuilder extends React.Component { if (isEmptyValue) { return [false, 'Value cannot be empty']; } + if (this.state.newJwtField === 'jwksUri' && !isValidUrl(this.state.newValues)) { + return [false, 'jwsUri is not a valid Uri']; + } return [true, '']; }; diff --git a/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleList.tsx b/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleList.tsx index d20ce4b779..6cb9a33201 100644 --- a/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleList.tsx +++ b/src/pages/IstioConfigNew/RequestAuthorizationForm/JwtRuleList.tsx @@ -46,6 +46,11 @@ class JwtRuleList extends React.Component { audiences: [{formatJwtField('audiences', jwtRule)}] ) : undefined} + {jwtRule.jwks ? ( +
+ jwks: [{formatJwtField('jwks', jwtRule)}] +
+ ) : undefined} {jwtRule.jwksUri ? (
jwksUri: [{formatJwtField('jwksUri', jwtRule)}] @@ -66,7 +71,7 @@ class JwtRuleList extends React.Component { outputPayloadToHeader: [{formatJwtField('outputPayloadToHeader', jwtRule)}]
) : undefined} - {jwtRule.forwardOriginalToken ? ( + {jwtRule.forwardOriginalToken !== undefined ? (
forwardOriginalToken: [{formatJwtField('forwardOriginalToken', jwtRule)}]
diff --git a/src/utils/IstioConfigUtils.ts b/src/utils/IstioConfigUtils.ts index bc0be85636..0352de9179 100644 --- a/src/utils/IstioConfigUtils.ts +++ b/src/utils/IstioConfigUtils.ts @@ -126,3 +126,12 @@ export const isServerHostValid = (serverHost: string, nsMandatory: boolean): boo export const isValidIp = (ip: string): boolean => { return ipRegexp.test(ip); }; + +export const isValidUrl = (url: string): boolean => { + try { + new URL(url); + } catch (_) { + return false; + } + return true; +}; diff --git a/src/utils/__tests__/IstioConfigUtils.test.ts b/src/utils/__tests__/IstioConfigUtils.test.ts index 51090919e7..17074fea92 100644 --- a/src/utils/__tests__/IstioConfigUtils.test.ts +++ b/src/utils/__tests__/IstioConfigUtils.test.ts @@ -1,4 +1,4 @@ -import { isServerHostValid, mergeJsonPatch } from '../IstioConfigUtils'; +import { isServerHostValid, isValidUrl, mergeJsonPatch } from '../IstioConfigUtils'; describe('Validate JSON Patchs', () => { const gateway: object = { @@ -83,3 +83,15 @@ describe('Validate Gateway/Sidecar Server Host ', () => { expect(isServerHostValid('bookinf*/reviews', true)).toBeFalsy(); }); }); + +describe('Validate bad urls', () => { + it('Good urls', () => { + expect(isValidUrl('http://www.googleapis.com/oauth2/v1/certs')).toBeTruthy(); + expect(isValidUrl('https://www.googleapis.com/oauth2/v1/certs')).toBeTruthy(); + }); + + it('Bad urls', () => { + expect(isValidUrl('ramdom')).toBeFalsy(); + expect(isValidUrl('123test')).toBeFalsy(); + }); +}); From cfa68b7576c532520be26cf617c71b1686553a24 Mon Sep 17 00:00:00 2001 From: Lucas Ponce Date: Tue, 26 May 2020 11:21:04 +0200 Subject: [PATCH 12/12] Fix debug console logs --- src/pages/IstioConfigNew/RequestAuthenticationForm.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx b/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx index c850b05074..f4165ba75d 100644 --- a/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx +++ b/src/pages/IstioConfigNew/RequestAuthenticationForm.tsx @@ -119,8 +119,6 @@ class RequestAuthenticationForm extends React.Component { - console.log('TODELETE onAddJwtRule'); - console.log(jwtRule); this.setState( (prevState) => { prevState.jwtRules.push(jwtRule);