Repository navigation
feat: [LKEAPIFW-428] LKE clusters should have IP ACL integration on CM (part 1) #10968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
693eaf6
7817a17
13b644f
9ead96e
1eec5c0
8c5534f
d41ff96
265a3ca
bc7af8a
903ce45
2bb302b
27bbbbb
4af433e
1235902
39610eb
672ad55
900e18f
b324a6b
fcecc60
34da662
1a70f0b
ecbc30c
a5a6b4a
6183ec0
dbe9387
a67a414
10257fb
d9082e5
6bc1260
788b1e0
c0af136
af7080d
0188f60
1582ac5
c871570
b9e12c7
2504e61
b0d9a8a
169427a
23b2797
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@linode/api-v4": Added | ||
| --- | ||
|
|
||
| ACL related endpoints and types for LKE clusters ([#10968](https://github.com/linode/manager/pull/10968)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,8 +60,22 @@ export interface KubernetesDashboardResponse { | |
| url: string; | ||
| } | ||
|
|
||
| export interface KubernetesControlPlaneACLPayload { | ||
| acl: ControlPlaneACLOptions; | ||
| } | ||
|
|
||
| export interface ControlPlaneACLOptions { | ||
| enabled?: boolean; | ||
| 'revision-id'?: string; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems quite unusual for the API to introduce a hyphened |
||
| addresses?: null | { | ||
| ipv4?: null | string[]; | ||
| ipv6?: null | string[]; | ||
| }; | ||
| } | ||
|
|
||
| export interface ControlPlaneOptions { | ||
| high_availability?: boolean; | ||
| acl?: ControlPlaneACLOptions; | ||
| } | ||
|
|
||
| export interface CreateKubeClusterPayload { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@linode/manager": Added | ||
| --- | ||
|
|
||
| IP ACL integration to LKE clusters ([#10968](https://github.com/linode/manager/pull/10968)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,8 +46,14 @@ import { Interception } from 'cypress/types/net-stubbing'; | |
| const expectedGranularityArray = ['Auto', '1 day', '1 hr', '5 min']; | ||
| const timeDurationToSelect = 'Last 24 Hours'; | ||
|
|
||
| const { metrics, id, serviceType, dashboardName, region, resource } = | ||
| widgetDetails.linode; | ||
| const { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like this file and |
||
| metrics, | ||
| id, | ||
| serviceType, | ||
| dashboardName, | ||
| region, | ||
| resource, | ||
| } = widgetDetails.linode; | ||
|
|
||
| const dashboard = dashboardFactory.build({ | ||
| label: dashboardName, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,5 @@ | ||
| import Close from '@mui/icons-material/Close'; | ||
| import { InputBaseProps } from '@mui/material/InputBase'; | ||
| import Grid from '@mui/material/Unstable_Grid2'; | ||
| import { Theme } from '@mui/material/styles'; | ||
| import * as React from 'react'; | ||
| import { makeStyles } from 'tss-react/mui'; | ||
|
|
||
|
|
@@ -13,7 +11,10 @@ import { StyledLinkButtonBox } from 'src/components/SelectFirewallPanel/SelectFi | |
| import { TextField } from 'src/components/TextField'; | ||
| import { TooltipIcon } from 'src/components/TooltipIcon'; | ||
| import { Typography } from 'src/components/Typography'; | ||
| import { ExtendedIP } from 'src/utilities/ipUtils'; | ||
|
|
||
| import type { InputBaseProps } from '@mui/material/InputBase'; | ||
| import type { Theme } from '@mui/material/styles'; | ||
| import type { ExtendedIP } from 'src/utilities/ipUtils'; | ||
|
|
||
| const useStyles = makeStyles()((theme: Theme) => ({ | ||
| addIP: { | ||
|
|
@@ -57,7 +58,7 @@ const useStyles = makeStyles()((theme: Theme) => ({ | |
| }, | ||
| })); | ||
|
|
||
| interface Props { | ||
| export interface MultipeIPInputProps { | ||
| buttonText?: string; | ||
| className?: string; | ||
| disabled?: boolean; | ||
|
|
@@ -67,6 +68,7 @@ interface Props { | |
| helperText?: string; | ||
| inputProps?: InputBaseProps; | ||
| ips: ExtendedIP[]; | ||
| isLinkStyled?: boolean; | ||
| onBlur?: (ips: ExtendedIP[]) => void; | ||
| onChange: (ips: ExtendedIP[]) => void; | ||
| placeholder?: string; | ||
|
|
@@ -75,7 +77,7 @@ interface Props { | |
| tooltip?: string; | ||
| } | ||
|
|
||
| export const MultipleIPInput = React.memo((props: Props) => { | ||
| export const MultipleIPInput = React.memo((props: MultipeIPInputProps) => { | ||
| const { | ||
| buttonText, | ||
| className, | ||
|
|
@@ -85,6 +87,7 @@ export const MultipleIPInput = React.memo((props: Props) => { | |
| forVPCIPv4Ranges, | ||
| helperText, | ||
| ips, | ||
| isLinkStyled, | ||
| onBlur, | ||
| onChange, | ||
| placeholder, | ||
|
|
@@ -130,21 +133,22 @@ export const MultipleIPInput = React.memo((props: Props) => { | |
| return null; | ||
| } | ||
|
|
||
| const addIPButton = forVPCIPv4Ranges ? ( | ||
| <StyledLinkButtonBox> | ||
| <LinkButton onClick={addNewInput}>{buttonText}</LinkButton> | ||
| </StyledLinkButtonBox> | ||
| ) : ( | ||
| <Button | ||
| buttonType="secondary" | ||
| className={classes.addIP} | ||
| compactX | ||
| disabled={disabled} | ||
| onClick={addNewInput} | ||
| > | ||
| {buttonText ?? 'Add an IP'} | ||
| </Button> | ||
| ); | ||
| const addIPButton = | ||
| forVPCIPv4Ranges || isLinkStyled ? ( | ||
| <StyledLinkButtonBox sx={{ marginTop: isLinkStyled ? '8px' : '12px' }}> | ||
| <LinkButton onClick={addNewInput}>{buttonText}</LinkButton> | ||
| </StyledLinkButtonBox> | ||
|
Comment on lines
+137
to
+140
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. introduced a new prop,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we just pass
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm - having an π€ing more |
||
| ) : ( | ||
| <Button | ||
| buttonType="secondary" | ||
| className={classes.addIP} | ||
| compactX | ||
| disabled={disabled} | ||
| onClick={addNewInput} | ||
| > | ||
| {buttonText ?? 'Add an IP'} | ||
| </Button> | ||
| ); | ||
|
|
||
| return ( | ||
| <div className={cx(classes.root, className)}> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import * as React from 'react'; | ||
|
|
||
| import { MultipleIPInput } from './MultipleIPInput'; | ||
|
|
||
| import type { MultipeIPInputProps } from './MultipleIPInput'; | ||
| import type { FieldError, Merge } from 'react-hook-form'; | ||
| import type { ExtendedIP } from 'src/utilities/ipUtils'; | ||
|
|
||
| interface Props extends Omit<MultipeIPInputProps, 'ips' | 'onChange'> { | ||
| ipErrors?: Merge<FieldError, (FieldError | undefined)[]>; | ||
| nonExtendedIPs: string[]; | ||
| onNonExtendedIPChange: (ips: string[]) => void; | ||
| } | ||
|
|
||
| /** | ||
| * Quick wrapper for MultipleIPInput so that we do not have to directly use the type ExtendedIP (which has its own error field) | ||
| * | ||
| * I wanted to avoid touching MultipleIPInput too much, since a lot of other flows use that component. This component was | ||
| * made with 'react-hook-form' in mind, taking in 'react-hook-form' errors and mapping them to the given (non | ||
| * extended) IPs. We might eventually try to completely remove the ExtendedIP type - see | ||
| * https://github.com/linode/manager/pull/10968#discussion_r1800089369 for context | ||
| */ | ||
| export const MultipleNonExtendedIPInput = (props: Props) => { | ||
| const { ipErrors, nonExtendedIPs, onNonExtendedIPChange, ...rest } = props; | ||
|
|
||
| const extendedIPs: ExtendedIP[] = | ||
| nonExtendedIPs.map((ip, idx) => { | ||
| return { | ||
| address: ip, | ||
| error: ipErrors ? ipErrors[idx]?.message : '', | ||
| }; | ||
| }) ?? []; | ||
|
|
||
| return ( | ||
| <MultipleIPInput | ||
| {...rest} | ||
| onChange={(ips) => { | ||
| const _ips = ips.map((ip) => { | ||
| return ip.address; | ||
| }); | ||
| onNonExtendedIPChange(_ips); | ||
| }} | ||
| ips={extendedIPs} | ||
| /> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| import { FormLabel } from '@mui/material'; | ||
| import * as React from 'react'; | ||
|
|
||
| import { Box } from 'src/components/Box'; | ||
| import { ErrorMessage } from 'src/components/ErrorMessage'; | ||
| import { FormControl } from 'src/components/FormControl'; | ||
| import { FormControlLabel } from 'src/components/FormControlLabel'; | ||
| import { MultipleIPInput } from 'src/components/MultipleIPInput/MultipleIPInput'; | ||
| import { Notice } from 'src/components/Notice/Notice'; | ||
| import { Toggle } from 'src/components/Toggle/Toggle'; | ||
| import { Typography } from 'src/components/Typography'; | ||
| import { validateIPs } from 'src/utilities/ipUtils'; | ||
|
|
||
| import type { ExtendedIP } from 'src/utilities/ipUtils'; | ||
|
|
||
| export interface ControlPlaneACLProps { | ||
| enableControlPlaneACL: boolean; | ||
| errorText: string | undefined; | ||
| handleIPv4Change: (ips: ExtendedIP[]) => void; | ||
| handleIPv6Change: (ips: ExtendedIP[]) => void; | ||
| ipV4Addr: ExtendedIP[]; | ||
| ipV6Addr: ExtendedIP[]; | ||
| setControlPlaneACL: (enabled: boolean) => void; | ||
| } | ||
|
|
||
| export const ControlPlaneACLPane = (props: ControlPlaneACLProps) => { | ||
| const { | ||
| enableControlPlaneACL, | ||
| errorText, | ||
| handleIPv4Change, | ||
| handleIPv6Change, | ||
| ipV4Addr, | ||
| ipV6Addr, | ||
| setControlPlaneACL, | ||
| } = props; | ||
|
|
||
| return ( | ||
| <> | ||
| <FormControl data-testid="control-plane-ipacl-form"> | ||
| <FormLabel id="ipacl-radio-buttons-group-label"> | ||
| <Typography variant="inherit">Control Plane ACL</Typography> | ||
| </FormLabel> | ||
| {errorText && ( | ||
| <Notice spacingTop={8} variant="error"> | ||
| <ErrorMessage message={errorText} />{' '} | ||
| </Notice> | ||
| )} | ||
| <Typography mb={1} sx={{ width: '85%' }}> | ||
| Enable an access control list (ACL) on your LKE cluster to restrict | ||
| access to your clusterβs control plane. When enabled, only the IP | ||
| addresses and ranges specified by you can connect to the control | ||
| plane. | ||
| </Typography> | ||
| <FormControlLabel | ||
| control={ | ||
| <Toggle | ||
| checked={enableControlPlaneACL} | ||
| name="ipacl-checkbox" | ||
| onChange={() => setControlPlaneACL(!enableControlPlaneACL)} | ||
| /> | ||
| } | ||
| label="Enable Control Plane ACL" | ||
| /> | ||
| </FormControl> | ||
| {enableControlPlaneACL && ( | ||
| <Box sx={{ marginBottom: 3, maxWidth: 450 }}> | ||
| <MultipleIPInput | ||
| onBlur={(_ips: ExtendedIP[]) => { | ||
| const validatedIPs = validateIPs(_ips, { | ||
| allowEmptyAddress: true, | ||
| errorMessage: 'Must be a valid IPv4 address.', | ||
| }); | ||
| handleIPv4Change(validatedIPs); | ||
| }} | ||
| buttonText="Add IPv4 Address" | ||
| ips={ipV4Addr} | ||
| isLinkStyled | ||
| onChange={handleIPv4Change} | ||
| placeholder="0.0.0.0/0" | ||
| title="IPv4 Addresses or CIDRs" | ||
| /> | ||
| <Box marginTop={2}> | ||
| <MultipleIPInput | ||
| onBlur={(_ips: ExtendedIP[]) => { | ||
| const validatedIPs = validateIPs(_ips, { | ||
| allowEmptyAddress: true, | ||
| errorMessage: 'Must be a valid IPv6 address.', | ||
| }); | ||
| handleIPv6Change(validatedIPs); | ||
| }} | ||
| buttonText="Add IPv6 Address" | ||
| ips={ipV6Addr} | ||
| isLinkStyled | ||
| onChange={handleIPv6Change} | ||
| placeholder="::/0" | ||
| title="IPv6 Addresses or CIDRs" | ||
| /> | ||
| </Box> | ||
| </Box> | ||
| )} | ||
| </> | ||
| ); | ||
| }; |
Uh oh!
There was an error while loading. Please reload this page.