diff --git a/src/api-v2.authz.test.ts b/src/api-v2.authz.test.ts index b3ed580b..99714a77 100644 --- a/src/api-v2.authz.test.ts +++ b/src/api-v2.authz.test.ts @@ -1385,6 +1385,24 @@ describe('API V2 authz tests', () => { ingress: { mode: 'AllowAll' }, }, }) + const createIngressAllowOnlyNetpol = (fromLabelValue: string, toLabelValue = 'frontend') => + createTeamResource('AplTeamNetworkControl', { + ruleType: { + type: 'ingress', + ingress: { + mode: 'AllowOnly', + toLabelName: 'app', + toLabelValue, + allow: [ + { + fromNamespace: 'team1', + fromLabelName: 'app', + fromLabelValue, + }, + ], + }, + }, + }) describe('Platform Admin', () => { test('platform admin can get all network policies', async () => { @@ -1401,6 +1419,48 @@ describe('API V2 authz tests', () => { .expect(200) }) + test('rejects multiline network policy label values', async () => { + const netpol = createIngressAllowOnlyNetpol('foo\nbar') + + const response = await agent + .post('/v2/teams/team1/netpols') + .send(netpol) + .set('Authorization', `Bearer ${teamMemberToken}`) + + expect(response.status).toBe(400) + }) + + it.each(['foo\nbar', 'foo\r\nbar', '{{ malicious }}', 'foo: bar', 'foo bar', '---'])( + 'rejects invalid label value %p', + async (fromLabelValue) => { + const response = await agent + .post('/v2/teams/team1/netpols') + .send(createIngressAllowOnlyNetpol(fromLabelValue)) + .set('Authorization', `Bearer ${teamMemberToken}`) + + expect(response.status).toBe(400) + }, + ) + + it.each(['app', 'my-app', 'my_app', 'my.app', 'APP123', ''])( + 'accepts Kubernetes label value %p', + async (fromLabelValue) => { + await agent + .post('/v2/teams/team1/netpols') + .send(createIngressAllowOnlyNetpol(fromLabelValue)) + .set('Authorization', `Bearer ${teamMemberToken}`) + .expect(200) + }, + ) + + test('rejects invalid to-label value on ingress selector', async () => { + await agent + .post('/v2/teams/team1/netpols') + .send(createIngressAllowOnlyNetpol('frontend', 'foo\nbar')) + .set('Authorization', `Bearer ${teamMemberToken}`) + .expect(400) + }) + test('team member can get team network policies', async () => { await agent.get('/v2/teams/team1/netpols').set('Authorization', `Bearer ${teamMemberToken}`).expect(200) }) diff --git a/src/openapi/definitions.yaml b/src/openapi/definitions.yaml index 763ddee6..375d8ff2 100644 --- a/src/openapi/definitions.yaml +++ b/src/openapi/definitions.yaml @@ -843,6 +843,14 @@ kms: labels: $ref: '#/annotations' description: A set of labels. +kubernetesLabelName: + type: string + maxLength: 63 + pattern: '^(?![\s\S]*[\r\n])[A-Za-z0-9](?:[-._A-Za-z0-9]{0,61}[A-Za-z0-9])?$' +kubernetesLabelValue: + type: string + maxLength: 63 + pattern: '^(?:$|(?![\s\S]*[\r\n])[A-Za-z0-9](?:[-._A-Za-z0-9]{0,61}[A-Za-z0-9])?)$' logLevel: title: Log Level type: string diff --git a/src/openapi/netpol.yaml b/src/openapi/netpol.yaml index e1012e15..accc8c27 100644 --- a/src/openapi/netpol.yaml +++ b/src/openapi/netpol.yaml @@ -52,12 +52,12 @@ ruleType: type: object properties: toLabelName: - type: string + $ref: 'definitions.yaml#/kubernetesLabelName' title: Selector label name description: 'The name of the Pod selector label.' example: app toLabelValue: - type: string + $ref: 'definitions.yaml#/kubernetesLabelValue' title: Selector label value description: 'The value of the Pod selector label.' example: my-app @@ -79,12 +79,12 @@ ruleType: title: Namespace name description: 'The name of the namespace.' fromLabelName: - type: string + $ref: 'definitions.yaml#/kubernetesLabelName' title: Selector label name description: 'The name of the Pod selector label.' example: app fromLabelValue: - type: string + $ref: 'definitions.yaml#/kubernetesLabelValue' title: Selector label value description: 'The value of the Pod selector label.' example: my-app diff --git a/src/patterns.test.ts b/src/patterns.test.ts index e52af9fd..98206311 100644 --- a/src/patterns.test.ts +++ b/src/patterns.test.ts @@ -104,6 +104,10 @@ const redosCases: Record = { k8sName: [`${'a-'.repeat(50_000)}!`], + kubernetesLabelValue: [`${'a-'.repeat(50_000)}!`, `${'a.'.repeat(50_000)}!`], + + kubernetesLabelName: [`${'a-'.repeat(50_000)}!`, `${'a.'.repeat(50_000)}!`], + idName: [`${'a-'.repeat(50_000)}!`], settingsName: [`${'aB'.repeat(50_000)}!`, `${'a-'.repeat(50_000)}!`], @@ -147,6 +151,8 @@ describe('OpenAPI definition regex patterns', () => { 'email', 'hostPort', 'k8sName', + 'kubernetesLabelValue', + 'kubernetesLabelName', 'idName', 'quantityCpu', 'quantityMem', @@ -242,6 +248,59 @@ describe('OpenAPI definition regex patterns', () => { }) }) + describe('kubernetesLabelValue', () => { + it('accepts valid Kubernetes label values', () => { + expectValid('kubernetesLabelValue', ['', 'app', 'my-app', 'my_app', 'my.app', 'APP123', 'a'.repeat(63)]) + }) + + it('rejects invalid Kubernetes label values', () => { + expectInvalid('kubernetesLabelValue', [ + '-app', + 'app-', + '.app', + 'app.', + '_app', + 'app_', + 'app value', + 'foo\nbar', + 'foo\r\nbar', + 'app\n', + 'app\r\n', + '{{ malicious }}', + 'foo: bar', + '---', + 'a'.repeat(64), + ]) + }) + }) + + describe('kubernetesLabelName', () => { + it('accepts valid Kubernetes label names', () => { + expectValid('kubernetesLabelName', ['app', 'my-app', 'my_app', 'my.app', 'APP123', 'a'.repeat(63)]) + }) + + it('rejects invalid Kubernetes label names', () => { + expectInvalid('kubernetesLabelName', [ + '', + '-app', + 'app-', + '.app', + 'app.', + '_app', + 'app_', + 'app name', + 'foo\nbar', + 'foo\r\nbar', + 'app\n', + 'app\r\n', + '{{ malicious }}', + 'foo: bar', + '---', + 'a'.repeat(64), + ]) + }) + }) + describe('idName', () => { it('accepts valid ID names', () => { expectValid('idName', ['a', 'secret', 'secret-name', 'secret1'])