Skip to content

Commit e031d80

Browse files
committed
Auth failure listener page with basic get, delete dummy create
Signed-off-by: Derek Ho <[email protected]>
1 parent 76c176a commit e031d80

File tree

7 files changed

+210
-0
lines changed

7 files changed

+210
-0
lines changed

common/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export enum ResourceType {
8383
tenantsConfigureTab = 'tenantsConfigureTab',
8484
auth = 'auth',
8585
auditLogging = 'auditLogging',
86+
authFailureListeners = 'authFailureListeners',
8687
}
8788

8889
/**

public/apps/configuration/app-router.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { ResourceType } from '../../../common';
4141
import { buildHashUrl, buildUrl } from './utils/url-builder';
4242
import { CrossPageToast } from './cross-page-toast';
4343
import { getDataSourceFromUrl, LocalCluster } from '../../utils/datasource-utils';
44+
import { AuthFailureListeners } from './panels/auth-failure-listeners';
4445

4546
const LANDING_PAGE_URL = '/getstarted';
4647

@@ -77,6 +78,10 @@ export const ROUTE_MAP: { [key: string]: RouteItem } = {
7778
name: 'Audit logs',
7879
href: buildUrl(ResourceType.auditLogging),
7980
},
81+
[ResourceType.authFailureListeners]: {
82+
name: 'Rate limiting',
83+
href: buildUrl(ResourceType.authFailureListeners),
84+
},
8085
};
8186

8287
const getRouteList = (multitenancyEnabled: boolean) => {
@@ -262,6 +267,13 @@ export function AppRouter(props: AppDependencies) {
262267
return <GetStarted {...props} />;
263268
}}
264269
/>
270+
<Route
271+
path={ROUTE_MAP.authFailureListeners.href}
272+
render={() => {
273+
setGlobalBreadcrumbs();
274+
return <AuthFailureListeners {...props} />;
275+
}}
276+
/>
265277
{multitenancyEnabled && (
266278
<Route
267279
path={ROUTE_MAP.tenants.href}

public/apps/configuration/constants.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const API_ENDPOINT = API_PREFIX + '/configuration';
2020
export const API_ENDPOINT_ROLES = API_ENDPOINT + '/roles';
2121
export const API_ENDPOINT_ROLESMAPPING = API_ENDPOINT + '/rolesmapping';
2222
export const API_ENDPOINT_ACTIONGROUPS = API_ENDPOINT + '/actiongroups';
23+
export const API_ENDPOINT_AUTHFAILURELISTENERS = API_ENDPOINT + '/authfailurelisteners';
2324
export const API_ENDPOINT_TENANTS = API_ENDPOINT + '/tenants';
2425
export const API_ENDPOINT_MULTITENANCY = API_PREFIX + '/multitenancy/tenant';
2526
export const API_ENDPOINT_TENANCY_CONFIGS = API_ENDPOINT + '/tenancy/config';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
import { EuiButton, EuiInMemoryTable, EuiIcon } from '@elastic/eui';
17+
import React, { useContext, useState } from 'react';
18+
import { AppDependencies } from '../../types';
19+
import { API_ENDPOINT_AUTHFAILURELISTENERS } from '../constants';
20+
import { createRequestContextWithDataSourceId } from '../utils/request-utils';
21+
import { SecurityPluginTopNavMenu } from '../top-nav-menu';
22+
import { DataSourceContext } from '../app-router';
23+
import { getResourceUrl } from '../utils/resource-utils';
24+
25+
export function AuthFailureListeners(props: AppDependencies) {
26+
const dataSourceEnabled = !!props.depsStart.dataSource?.dataSourceEnabled;
27+
const { dataSource, setDataSource } = useContext(DataSourceContext)!;
28+
29+
const [listeners, setListeners] = useState([]);
30+
31+
const fetchData = async () => {
32+
const data = await createRequestContextWithDataSourceId(dataSource.id).httpGet<any>({
33+
http: props.coreStart.http,
34+
url: API_ENDPOINT_AUTHFAILURELISTENERS,
35+
});
36+
setListeners(data.data);
37+
};
38+
39+
const handleDelete = async (name: string) => {
40+
await createRequestContextWithDataSourceId(dataSource.id).httpDelete<any>({
41+
http: props.coreStart.http,
42+
url: getResourceUrl(API_ENDPOINT_AUTHFAILURELISTENERS, name),
43+
});
44+
};
45+
46+
const createDummyAuthFailureListener = async () => {
47+
await createRequestContextWithDataSourceId(dataSource.id).httpPost<any>({
48+
http: props.coreStart.http,
49+
url: getResourceUrl(API_ENDPOINT_AUTHFAILURELISTENERS, 'test'),
50+
body: {
51+
type: 'ip',
52+
authentication_backend: 'test',
53+
allowed_tries: 10,
54+
time_window_seconds: 3600,
55+
block_expiry_seconds: 600,
56+
max_blocked_clients: 100000,
57+
max_tracked_clients: 100000,
58+
},
59+
});
60+
};
61+
62+
const columns = [
63+
{
64+
field: 'name',
65+
name: 'Name',
66+
},
67+
{
68+
field: 'type',
69+
name: 'Type',
70+
},
71+
{
72+
field: 'authentication_backend',
73+
name: 'Authentication backend',
74+
},
75+
{
76+
field: 'allowed_tries',
77+
name: 'Allowed tries',
78+
},
79+
{
80+
field: 'time_window_seconds',
81+
name: 'Time window (sec)',
82+
},
83+
{
84+
field: 'block_expiry_seconds',
85+
name: 'Block expiry (sec)',
86+
},
87+
{
88+
field: 'max_blocked_clients',
89+
name: 'Max blocked clients',
90+
},
91+
{
92+
field: 'max_tracked_clients',
93+
name: 'Max tracked clients',
94+
},
95+
{
96+
field: 'name',
97+
name: 'Actions',
98+
render: (name) => <EuiIcon type="trash" onClick={() => handleDelete(name)} />,
99+
},
100+
];
101+
102+
return (
103+
<>
104+
<div className="panel-restrict-width">
105+
<SecurityPluginTopNavMenu
106+
{...props}
107+
dataSourcePickerReadOnly={false}
108+
setDataSource={setDataSource}
109+
selectedDataSource={dataSource}
110+
/>
111+
</div>
112+
<EuiButton onClick={fetchData}>GET</EuiButton>
113+
<EuiInMemoryTable
114+
tableLayout={'auto'}
115+
columns={columns}
116+
items={listeners}
117+
itemId={'domain_name'}
118+
pagination={true}
119+
sorting={true}
120+
/>
121+
122+
<EuiButton onClick={createDummyAuthFailureListener}>CREATE</EuiButton>
123+
</>
124+
);
125+
}

public/apps/configuration/test/__snapshots__/app-router.test.tsx.snap

+4
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,10 @@ exports[`SecurityPluginTopNavMenu renders DataSourceMenu when dataSource is enab
484484
path="/getstarted"
485485
render={[Function]}
486486
/>
487+
<Route
488+
path="/authFailureListeners"
489+
render={[Function]}
490+
/>
487491
<Route
488492
path="/tenants"
489493
render={[Function]}

server/backend/opensearch_security_configuration_plugin.ts

+10
Original file line numberDiff line numberDiff line change
@@ -220,4 +220,14 @@ export default function (Client: any, config: any, components: any) {
220220
fmt: '/_plugins/_security/api/audit/config',
221221
},
222222
});
223+
224+
/**
225+
* Gets auth failure listeners.
226+
*/
227+
Client.prototype.opensearch_security.prototype.getAuthFailureListeners = ca({
228+
method: 'GET',
229+
url: {
230+
fmt: '/_plugins/_security/api/authfailurelisteners',
231+
},
232+
});
223233
}

server/routes/index.ts

+57
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,24 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) {
6767
current_password: schema.string(),
6868
});
6969

70+
const authFailureListenersSchema = schema.object({
71+
allowed_tries: schema.number(),
72+
authentication_backend: schema.string(),
73+
block_expiry_seconds: schema.number(),
74+
max_blocked_clients: schema.number(),
75+
max_tracked_clients: schema.number(),
76+
time_window_seconds: schema.number(),
77+
type: schema.string(),
78+
});
79+
7080
const schemaMap: any = {
7181
internalusers: internalUserSchema,
7282
actiongroups: actionGroupSchema,
7383
rolesmapping: roleMappingSchema,
7484
roles: roleSchema,
7585
tenants: tenantSchema,
7686
account: accountSchema,
87+
authfailurelisteners: authFailureListenersSchema,
7788
};
7889

7990
function validateRequestBody(resourceName: string, requestBody: any): any {
@@ -694,6 +705,52 @@ export function defineRoutes(router: IRouter, dataSourceEnabled: boolean) {
694705
}
695706
);
696707

708+
/**
709+
* Gets auth failure listeners。
710+
*
711+
* Sample payload:
712+
* [
713+
* { ??? }
714+
*
715+
* ]
716+
*/
717+
router.get(
718+
{
719+
path: `${API_PREFIX}/configuration/authfailurelisteners`,
720+
validate: {
721+
query: schema.object({
722+
dataSourceId: schema.maybe(schema.string()),
723+
}),
724+
},
725+
},
726+
async (
727+
context,
728+
request,
729+
response
730+
): Promise<IOpenSearchDashboardsResponse<any | ResponseError>> => {
731+
try {
732+
const esResp = await wrapRouteWithDataSource(
733+
dataSourceEnabled,
734+
context,
735+
request,
736+
'opensearch_security.getAuthFailureListeners'
737+
);
738+
739+
return response.ok({
740+
body: {
741+
total: Object.keys(esResp).length,
742+
data: esResp,
743+
},
744+
});
745+
} catch (error) {
746+
return response.custom({
747+
statusCode: error.statusCode,
748+
body: parseEsErrorResponse(error),
749+
});
750+
}
751+
}
752+
);
753+
697754
/**
698755
* Update audit log configuration。
699756
*

0 commit comments

Comments
 (0)