feat:adds barbican panel(named Key-Manager)
This change adds barbican panel for secrets and list similar to other panels and subpanel. Has Creation , Detail Secret Page included Change-Id: I33a7c4a4845529c5a6d44060728956701f50dccf Signed-off-by: Reet Srivastava <reet.srivastava@rackspace.com>
This commit is contained in:
@@ -37,6 +37,11 @@ export class BarbicanClient extends Base {
|
|||||||
key: 'containers',
|
key: 'containers',
|
||||||
responseKey: 'container',
|
responseKey: 'container',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'orders',
|
||||||
|
key: 'orders',
|
||||||
|
responseKey: 'order',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -315,7 +315,6 @@ export default class BaseForm extends React.Component {
|
|||||||
|
|
||||||
onOk = (values, containerProps, callback) => {
|
onOk = (values, containerProps, callback) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('onOk', values);
|
|
||||||
this.values = values;
|
this.values = values;
|
||||||
if (this.codeError) {
|
if (this.codeError) {
|
||||||
return;
|
return;
|
||||||
|
@@ -618,6 +618,29 @@ const renderMenu = (t) => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/key-manager',
|
||||||
|
name: t('Key Manager'),
|
||||||
|
key: 'keyManager',
|
||||||
|
icon: <ContainerOutlined />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/key-manager/secret',
|
||||||
|
name: t('Secrets'),
|
||||||
|
key: 'keyManagerSecret',
|
||||||
|
level: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: /^\/key-manager\/secret\/detail\/.[^/]+$/,
|
||||||
|
name: t('Secret Detail'),
|
||||||
|
key: 'secretDetail',
|
||||||
|
level: 2,
|
||||||
|
routePath: '/key-manager/secret/detail/:id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// path: '/management',
|
// path: '/management',
|
||||||
// name: t('Maintenance'),
|
// name: t('Maintenance'),
|
||||||
|
20
src/pages/barbican/App.jsx
Normal file
20
src/pages/barbican/App.jsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2025 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import renderRoutes from 'utils/RouterConfig';
|
||||||
|
import routes from './routes';
|
||||||
|
|
||||||
|
const App = (props) => renderRoutes(routes, props);
|
||||||
|
|
||||||
|
export default App;
|
50
src/pages/barbican/containers/Secret/Detail/BaseDetail.jsx
Normal file
50
src/pages/barbican/containers/Secret/Detail/BaseDetail.jsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2025 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { inject, observer } from 'mobx-react';
|
||||||
|
import Base from 'containers/BaseDetail';
|
||||||
|
|
||||||
|
export class BaseDetail extends Base {
|
||||||
|
get leftCardsStyle() {
|
||||||
|
return {
|
||||||
|
flex: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get leftCards() {
|
||||||
|
const cards = [this.contentCard];
|
||||||
|
return cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
get contentCard() {
|
||||||
|
const { payload } = this.props.detail;
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
content: <pre>{payload}</pre>,
|
||||||
|
copyable: {
|
||||||
|
text: payload,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
title: t('Secret Content'),
|
||||||
|
labelCol: 0,
|
||||||
|
contentCol: 24,
|
||||||
|
options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default inject('rootStore')(observer(BaseDetail));
|
104
src/pages/barbican/containers/Secret/Detail/index.jsx
Normal file
104
src/pages/barbican/containers/Secret/Detail/index.jsx
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
// Copyright 2025 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { inject, observer } from 'mobx-react';
|
||||||
|
import Base from 'containers/TabDetail';
|
||||||
|
import { SecretsStore } from 'stores/barbican/secrets';
|
||||||
|
import { Badge } from 'antd';
|
||||||
|
import actionConfigs from '../actions';
|
||||||
|
import BaseDetail from './BaseDetail';
|
||||||
|
|
||||||
|
export class SecretDetail extends Base {
|
||||||
|
init() {
|
||||||
|
this.store = new SecretsStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
get policy() {
|
||||||
|
return 'secret:get';
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return t('Secret Detail');
|
||||||
|
}
|
||||||
|
|
||||||
|
get listUrl() {
|
||||||
|
return this.getRoutePath('keyManagerSecret');
|
||||||
|
}
|
||||||
|
|
||||||
|
get actionConfigs() {
|
||||||
|
return actionConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
|
get detailInfos() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: t('ID'),
|
||||||
|
dataIndex: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Name'),
|
||||||
|
dataIndex: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Status'),
|
||||||
|
dataIndex: 'expiration',
|
||||||
|
valueRender: 'toLocalTime',
|
||||||
|
render: (value) => {
|
||||||
|
if (value) {
|
||||||
|
const isExpired = value && new Date(value) < new Date();
|
||||||
|
const statusText = t(isExpired ? 'Expired' : 'Active');
|
||||||
|
const statusColor = isExpired ? '#D32F45' : '#3C9E6C';
|
||||||
|
return <Badge color={statusColor} text={statusText} />;
|
||||||
|
}
|
||||||
|
return <Badge color="#3C9E6C" text="Active" />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Secret Type'),
|
||||||
|
dataIndex: 'secret_type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Algorithm'),
|
||||||
|
dataIndex: 'algorithm',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Mode'),
|
||||||
|
dataIndex: 'mode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Expiration'),
|
||||||
|
dataIndex: 'expiration',
|
||||||
|
valueRender: 'toLocalTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Created At'),
|
||||||
|
dataIndex: 'created',
|
||||||
|
valueRender: 'toLocalTime',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
get tabs() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: t('Detail Info'),
|
||||||
|
key: 'detail_info',
|
||||||
|
component: BaseDetail,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default inject('rootStore')(observer(SecretDetail));
|
462
src/pages/barbican/containers/Secret/actions/Create.jsx
Normal file
462
src/pages/barbican/containers/Secret/actions/Create.jsx
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
// Copyright 2025 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { inject, observer } from 'mobx-react';
|
||||||
|
import { ModalAction } from 'containers/Action';
|
||||||
|
import globalSecretsStore from 'stores/barbican/secrets';
|
||||||
|
import globalOrdersStore from 'stores/barbican/orders';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
// Constants for secret configuration
|
||||||
|
const SECRET_CONFIG = {
|
||||||
|
KEY: {
|
||||||
|
BIT_LENGTH: 256,
|
||||||
|
ALGORITHM: 'aes',
|
||||||
|
},
|
||||||
|
ASYMMETRIC: {
|
||||||
|
BIT_LENGTH: 2048,
|
||||||
|
ALGORITHM: 'rsa',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export class CreateSecret extends ModalAction {
|
||||||
|
static id = 'create-secret';
|
||||||
|
|
||||||
|
static title = t('Create Secret');
|
||||||
|
|
||||||
|
static policy = ['secrets:post', 'orders:post'];
|
||||||
|
|
||||||
|
static allowed = () => Promise.resolve(true);
|
||||||
|
|
||||||
|
static get modalSize() {
|
||||||
|
return 'large';
|
||||||
|
}
|
||||||
|
|
||||||
|
static CONTENT_TYPE_OPTIONS = [
|
||||||
|
{ label: 'text/plain', value: 'text/plain' },
|
||||||
|
{ label: 'application/octet-stream', value: 'application/octet-stream' },
|
||||||
|
{ label: 'application/x-pkcs12', value: 'application/x-pkcs12' },
|
||||||
|
{ label: 'application/x-pem-file', value: 'application/x-pem-file' },
|
||||||
|
];
|
||||||
|
|
||||||
|
static ALGORITHM_OPTIONS = [
|
||||||
|
{ label: 'AES', value: 'aes' },
|
||||||
|
{ label: 'DES', value: 'des' },
|
||||||
|
{ label: '3DES', value: '3des' },
|
||||||
|
{ label: 'RSA', value: 'rsa' },
|
||||||
|
{ label: 'DSA', value: 'dsa' },
|
||||||
|
{ label: 'EC', value: 'ec' },
|
||||||
|
];
|
||||||
|
|
||||||
|
static ASYMMETRIC_ALGORITHM_OPTIONS = [
|
||||||
|
{ label: 'RSA', value: 'rsa' },
|
||||||
|
{ label: 'DSA', value: 'dsa' },
|
||||||
|
];
|
||||||
|
|
||||||
|
static BIT_LENGTH_OPTIONS = [
|
||||||
|
{ label: '128', value: 128 },
|
||||||
|
{ label: '192', value: 192 },
|
||||||
|
{ label: '256', value: 256 },
|
||||||
|
{ label: '1024', value: 1024 },
|
||||||
|
{ label: '2048', value: 2048 },
|
||||||
|
{ label: '4096', value: 4096 },
|
||||||
|
];
|
||||||
|
|
||||||
|
static MODE_OPTIONS = [
|
||||||
|
{ label: 'CBC', value: 'cbc' },
|
||||||
|
{ label: 'CTR', value: 'ctr' },
|
||||||
|
];
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.secretsStore = globalSecretsStore;
|
||||||
|
this.ordersStore = globalOrdersStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return t('Create Secret');
|
||||||
|
}
|
||||||
|
|
||||||
|
get nameForStateUpdate() {
|
||||||
|
return ['creationType', 'secret_type', 'request_type', 'algorithm'];
|
||||||
|
}
|
||||||
|
|
||||||
|
onSecretTypeChange = (value) => {
|
||||||
|
const { creationType } = this.state;
|
||||||
|
|
||||||
|
if (!this.formRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (creationType === 'direct') {
|
||||||
|
this.formRef.current.setFieldsValue({
|
||||||
|
secret_type: value,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config =
|
||||||
|
value === 'key'
|
||||||
|
? SECRET_CONFIG.KEY
|
||||||
|
: value === 'asymmetric'
|
||||||
|
? SECRET_CONFIG.ASYMMETRIC
|
||||||
|
: { BIT_LENGTH: undefined, ALGORITHM: undefined };
|
||||||
|
|
||||||
|
this.formRef.current.setFieldsValue({
|
||||||
|
request_type: value,
|
||||||
|
bit_length: config.BIT_LENGTH,
|
||||||
|
algorithm: config.ALGORITHM,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
get defaultValue() {
|
||||||
|
return {
|
||||||
|
creationType: 'direct',
|
||||||
|
name: '',
|
||||||
|
payload: '',
|
||||||
|
payload_content_type: 'text/plain',
|
||||||
|
payload_content_encoding: '',
|
||||||
|
algorithm: 'aes',
|
||||||
|
bit_length: 256,
|
||||||
|
mode: 'cbc',
|
||||||
|
secret_type: 'opaque',
|
||||||
|
request_type: 'key',
|
||||||
|
expiration: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get creationTypeOptions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('Create Secret Directly'),
|
||||||
|
value: 'direct',
|
||||||
|
tip: t('Create a secret with your own payload content'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Create Secret via Order'),
|
||||||
|
value: 'order',
|
||||||
|
tip: t('Create a secret through order (for certificates, keys, etc.)'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
get requestTypeOptions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('Key'),
|
||||||
|
value: 'key',
|
||||||
|
tip: t('Symmetric keys, private keys, and passphrases'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Asymmetric Key'),
|
||||||
|
value: 'asymmetric',
|
||||||
|
tip: t('Asymmetric key pairs (public/private)'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
get secretTypeOptionsForDirect() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('Opaque'),
|
||||||
|
value: 'opaque',
|
||||||
|
tip: t('Default secret type for arbitrary data'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Symmetric'),
|
||||||
|
value: 'symmetric',
|
||||||
|
tip: t('Symmetric keys'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Public'),
|
||||||
|
value: 'public',
|
||||||
|
tip: t('Public keys'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Private'),
|
||||||
|
value: 'private',
|
||||||
|
tip: t('Private keys'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Certificate'),
|
||||||
|
value: 'certificate',
|
||||||
|
tip: t('Certificates'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Passphrase'),
|
||||||
|
value: 'passphrase',
|
||||||
|
tip: t('Passphrases'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
get algorithmOptions() {
|
||||||
|
const { secret_type, request_type } = this.state;
|
||||||
|
const currentType = secret_type || request_type;
|
||||||
|
|
||||||
|
if (currentType === 'asymmetric') {
|
||||||
|
return CreateSecret.ASYMMETRIC_ALGORITHM_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateSecret.ALGORITHM_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
get bitLengthOptions() {
|
||||||
|
return CreateSecret.BIT_LENGTH_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modeOptions() {
|
||||||
|
return CreateSecret.MODE_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
get formItems() {
|
||||||
|
const { creationType } = this.state;
|
||||||
|
const isDirect = creationType === 'direct';
|
||||||
|
const isOrder = creationType === 'order';
|
||||||
|
|
||||||
|
const baseItems = this.getBaseFormItems();
|
||||||
|
|
||||||
|
if (isDirect) {
|
||||||
|
return [...baseItems, ...this.getDirectFormItems()];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOrder) {
|
||||||
|
return [...baseItems, ...this.getOrderFormItems()];
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBaseFormItems() {
|
||||||
|
const { creationType } = this.state;
|
||||||
|
const isDirect = creationType === 'direct';
|
||||||
|
const isOrder = creationType === 'order';
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'creationType',
|
||||||
|
label: t('Creation Method'),
|
||||||
|
type: 'radio',
|
||||||
|
options: this.creationTypeOptions,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: t('Secret Name'),
|
||||||
|
type: 'input-name',
|
||||||
|
required: false,
|
||||||
|
withoutChinese: true,
|
||||||
|
tip: isOrder
|
||||||
|
? t('Optional. If not provided, a name will be auto-generated')
|
||||||
|
: t('Required for direct creation'),
|
||||||
|
rules: isDirect
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('Secret name is required for direct creation'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getDirectFormItems() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'payload',
|
||||||
|
label: t('Secret Payload'),
|
||||||
|
type: 'textarea',
|
||||||
|
required: true,
|
||||||
|
rows: 6,
|
||||||
|
placeholder: t('Enter the secret content'),
|
||||||
|
tip: t('The actual secret data to be stored'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'payload_content_type',
|
||||||
|
label: t('Payload Content Type'),
|
||||||
|
type: 'select',
|
||||||
|
options: CreateSecret.CONTENT_TYPE_OPTIONS,
|
||||||
|
required: true,
|
||||||
|
tip: t('Required when payload is supplied'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'payload_content_encoding',
|
||||||
|
label: t('Payload Content Encoding'),
|
||||||
|
type: 'input',
|
||||||
|
required: false,
|
||||||
|
tip: t(
|
||||||
|
'Required if payload content type is "application/octet-stream"'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'secret_type',
|
||||||
|
label: t('Secret Type'),
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t('Select Secret Type'),
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
...this.secretTypeOptionsForDirect,
|
||||||
|
],
|
||||||
|
required: false,
|
||||||
|
tip: t('Optional. Type of secret being stored (default: opaque)'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'algorithm',
|
||||||
|
label: t('Algorithm'),
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t('Select Algorithm'),
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
...this.algorithmOptions,
|
||||||
|
],
|
||||||
|
required: false,
|
||||||
|
tip: t('Optional. Algorithm used by the secret (default: aes)'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bit_length',
|
||||||
|
label: t('Bit Length'),
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t('Select Bit Length'),
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
...this.bitLengthOptions,
|
||||||
|
],
|
||||||
|
required: false,
|
||||||
|
tip: t('Optional. Bit length of the secret (default: 256)'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mode',
|
||||||
|
label: t('Mode'),
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t('Select Mode'),
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
...this.modeOptions,
|
||||||
|
],
|
||||||
|
required: false,
|
||||||
|
tip: t(
|
||||||
|
'Optional. Algorithm mode, used only for reference (default: cbc)'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiration',
|
||||||
|
label: t('Expiration Date'),
|
||||||
|
type: 'date-picker',
|
||||||
|
showToday: false,
|
||||||
|
disabledDate: (current) => current && current <= moment().endOf('d'),
|
||||||
|
tip: t('Optional. When the secret should expire (ISO 8601 format)'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrderFormItems() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'request_type',
|
||||||
|
label: t('Request Type'),
|
||||||
|
type: 'select',
|
||||||
|
options: this.requestTypeOptions,
|
||||||
|
required: true,
|
||||||
|
onChange: this.onSecretTypeChange,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'algorithm',
|
||||||
|
label: t('Algorithm'),
|
||||||
|
type: 'select',
|
||||||
|
options: this.algorithmOptions,
|
||||||
|
required: false,
|
||||||
|
tip: t(
|
||||||
|
'Optional. Algorithm to be used with the requested key (default: aes)'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bit_length',
|
||||||
|
label: t('Bit Length'),
|
||||||
|
type: 'select',
|
||||||
|
options: this.bitLengthOptions,
|
||||||
|
required: false,
|
||||||
|
tip: t(
|
||||||
|
'Optional. Bit length of the requested secret key (default: 256)'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mode',
|
||||||
|
label: t('Mode'),
|
||||||
|
type: 'select',
|
||||||
|
options: this.modeOptions,
|
||||||
|
required: false,
|
||||||
|
tip: t(
|
||||||
|
'Optional. Algorithm mode to be used with the requested key (default: cbc)'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'payload_content_type',
|
||||||
|
label: t('Payload Content Type'),
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'application/octet-stream',
|
||||||
|
value: 'application/octet-stream',
|
||||||
|
},
|
||||||
|
{ label: 'text/plain', value: 'text/plain' },
|
||||||
|
{ label: 'application/x-pkcs12', value: 'application/x-pkcs12' },
|
||||||
|
{
|
||||||
|
label: 'application/x-pem-file',
|
||||||
|
value: 'application/x-pem-file',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
required: false,
|
||||||
|
tip: t(
|
||||||
|
'Optional. Type/format of the secret to be generated (default: application/octet-stream)'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiration',
|
||||||
|
label: t('Expiration Date'),
|
||||||
|
type: 'date-picker',
|
||||||
|
showToday: false,
|
||||||
|
disabledDate: (current) => current && current <= moment().endOf('d'),
|
||||||
|
tip: t('Optional. Expiration time for the secret in ISO 8601 format'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickCancel = () => {
|
||||||
|
this.onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit = (values) => {
|
||||||
|
const { creationType, ...rest } = values;
|
||||||
|
|
||||||
|
if (creationType === 'direct') {
|
||||||
|
return this.secretsStore.create(rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (creationType === 'order') {
|
||||||
|
return this.ordersStore.create(rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(new Error('Invalid creation type'));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default inject('rootStore')(observer(CreateSecret));
|
46
src/pages/barbican/containers/Secret/actions/Delete.jsx
Normal file
46
src/pages/barbican/containers/Secret/actions/Delete.jsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2025 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { ConfirmAction } from 'containers/Action';
|
||||||
|
import globalSecretsStore from 'stores/barbican/secrets';
|
||||||
|
|
||||||
|
export default class DeleteAction extends ConfirmAction {
|
||||||
|
static policy = 'secret:delete';
|
||||||
|
|
||||||
|
static allowed = () => Promise.resolve(true);
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return 'delete';
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return t('Delete Secret');
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDanger() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get buttonText() {
|
||||||
|
return t('Delete');
|
||||||
|
}
|
||||||
|
|
||||||
|
get actionName() {
|
||||||
|
return t('delete secret');
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit = (data) => {
|
||||||
|
return globalSecretsStore.delete(data);
|
||||||
|
};
|
||||||
|
}
|
13
src/pages/barbican/containers/Secret/actions/index.js
Normal file
13
src/pages/barbican/containers/Secret/actions/index.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import CreateAction from './Create';
|
||||||
|
import DeleteAction from './Delete';
|
||||||
|
|
||||||
|
const actionConfigs = {
|
||||||
|
rowActions: {
|
||||||
|
firstAction: DeleteAction,
|
||||||
|
moreActions: [],
|
||||||
|
},
|
||||||
|
batchActions: [DeleteAction],
|
||||||
|
primaryActions: [CreateAction],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default actionConfigs;
|
91
src/pages/barbican/containers/Secret/index.jsx
Normal file
91
src/pages/barbican/containers/Secret/index.jsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2025 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { observer, inject } from 'mobx-react';
|
||||||
|
import BaseList from 'containers/List';
|
||||||
|
import globalSecretsStore from 'stores/barbican/secrets';
|
||||||
|
import { Badge } from 'antd';
|
||||||
|
import actionConfigs from './actions';
|
||||||
|
|
||||||
|
export class SecretList extends BaseList {
|
||||||
|
init() {
|
||||||
|
this.store = globalSecretsStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return t('Secrets');
|
||||||
|
}
|
||||||
|
|
||||||
|
get policy() {
|
||||||
|
return 'barbican:secret:get';
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumns() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: t('ID/Name'),
|
||||||
|
dataIndex: 'name',
|
||||||
|
routeName: this.getRouteName('secretDetail'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Status'),
|
||||||
|
dataIndex: 'expiration',
|
||||||
|
valueRender: 'toLocalTime',
|
||||||
|
render: (value) => {
|
||||||
|
if (value) {
|
||||||
|
const isExpired = value && new Date(value) < new Date();
|
||||||
|
const statusText = t(isExpired ? 'Expired' : 'Active');
|
||||||
|
const statusColor = isExpired ? '#D32F45' : '#3C9E6C';
|
||||||
|
return <Badge color={statusColor} text={statusText} />;
|
||||||
|
}
|
||||||
|
return <Badge color="#3C9E6C" text="Active" />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Algorithm'),
|
||||||
|
dataIndex: 'algorithm',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Expiration'),
|
||||||
|
dataIndex: 'expiration',
|
||||||
|
valueRender: 'toLocalTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Created At'),
|
||||||
|
dataIndex: 'created',
|
||||||
|
valueRender: 'toLocalTime',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
get actionConfigs() {
|
||||||
|
return actionConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
|
get searchFilters() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('Name'),
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Algorithm'),
|
||||||
|
name: 'algorithm',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default inject('rootStore')(observer(SecretList));
|
35
src/pages/barbican/routes/index.jsx
Normal file
35
src/pages/barbican/routes/index.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2025 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import BaseLayout from 'layouts/Basic';
|
||||||
|
import E404 from 'pages/base/containers/404';
|
||||||
|
import SecretList from '../containers/Secret';
|
||||||
|
import SecretDetail from '../containers/Secret/Detail';
|
||||||
|
|
||||||
|
const PATH = '/key-manager';
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: PATH,
|
||||||
|
component: BaseLayout,
|
||||||
|
routes: [
|
||||||
|
{ path: `${PATH}/secret`, component: SecretList, exact: true },
|
||||||
|
{
|
||||||
|
path: `${PATH}/secret/detail/:id`,
|
||||||
|
component: SecretDetail,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
{ path: '*', component: E404 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
@@ -50,6 +50,9 @@ const Database = lazy(() =>
|
|||||||
const Share = lazy(() =>
|
const Share = lazy(() =>
|
||||||
import(/* webpackChunkName: "share" */ 'pages/share/App')
|
import(/* webpackChunkName: "share" */ 'pages/share/App')
|
||||||
);
|
);
|
||||||
|
const Barbican = lazy(() =>
|
||||||
|
import(/* webpackChunkName: "barbican" */ 'pages/barbican/App')
|
||||||
|
);
|
||||||
const ContainerInfra = lazy(() =>
|
const ContainerInfra = lazy(() =>
|
||||||
import(/* webpackChunkName: "container-infra" */ 'pages/container-infra/App')
|
import(/* webpackChunkName: "container-infra" */ 'pages/container-infra/App')
|
||||||
);
|
);
|
||||||
@@ -111,6 +114,10 @@ export default [
|
|||||||
path: `/share`,
|
path: `/share`,
|
||||||
component: Share,
|
component: Share,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `/key-manager`,
|
||||||
|
component: Barbican,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `/container-infra`,
|
path: `/container-infra`,
|
||||||
component: ContainerInfra,
|
component: ContainerInfra,
|
||||||
|
@@ -71,6 +71,8 @@ export const policyMap = {
|
|||||||
'secret:decrypt',
|
'secret:decrypt',
|
||||||
'secret:delete',
|
'secret:delete',
|
||||||
'containers:post',
|
'containers:post',
|
||||||
|
'secrets:post',
|
||||||
|
'orders:post',
|
||||||
],
|
],
|
||||||
zun: ['capsule:', 'container:', 'host:get'],
|
zun: ['capsule:', 'container:', 'host:get'],
|
||||||
panko: ['segregation', 'telemetry:events:index'],
|
panko: ['segregation', 'telemetry:events:index'],
|
||||||
|
@@ -150,11 +150,45 @@ export class ContainersStore extends Base {
|
|||||||
});
|
});
|
||||||
// Determine if the certificate is used in the listener
|
// Determine if the certificate is used in the listener
|
||||||
this.updateItem(item, listeners);
|
this.updateItem(item, listeners);
|
||||||
// Fetch secrets payload
|
// Fetch secrets payload with proper decoding
|
||||||
const payloads = await Promise.all(
|
const payloads = await Promise.all(
|
||||||
secretIds.map((id) =>
|
secretIds.map(async (id) => {
|
||||||
this.payloadClient.list(id, {}, { headers: { Accept: 'text/plain' } })
|
try {
|
||||||
)
|
const payload = await this.payloadClient.list(id, null, {
|
||||||
|
headers: {
|
||||||
|
Accept: '*/*',
|
||||||
|
},
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Decode payload if it's binary
|
||||||
|
if (payload instanceof ArrayBuffer) {
|
||||||
|
const bytes = new Uint8Array(payload);
|
||||||
|
|
||||||
|
// Check if it's actually text by trying to decode it
|
||||||
|
const textDecoder = new TextDecoder('utf-8', { fatal: false });
|
||||||
|
const decodedText = textDecoder.decode(bytes);
|
||||||
|
|
||||||
|
// Check if the decoded text contains valid printable characters
|
||||||
|
const isValidText = /^[\x20-\x7E\n\r\t]*$/.test(decodedText);
|
||||||
|
|
||||||
|
if (isValidText) {
|
||||||
|
// It's valid text, use it
|
||||||
|
return decodedText;
|
||||||
|
}
|
||||||
|
// It's binary data, convert to base64
|
||||||
|
let binary = '';
|
||||||
|
for (let i = 0; i < bytes.byteLength; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
return btoa(binary);
|
||||||
|
}
|
||||||
|
return payload;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch payload for secret ${id}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
);
|
);
|
||||||
(payloads || []).forEach((it, index) => {
|
(payloads || []).forEach((it, index) => {
|
||||||
secret_refs[index].secret_info.payload = it;
|
secret_refs[index].secret_info.payload = it;
|
||||||
|
56
src/stores/barbican/orders.js
Normal file
56
src/stores/barbican/orders.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2025 99cloud
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import Base from 'stores/base';
|
||||||
|
import client from 'client';
|
||||||
|
import { action } from 'mobx';
|
||||||
|
|
||||||
|
export class OrdersStore extends Base {
|
||||||
|
get client() {
|
||||||
|
return client.barbican.orders;
|
||||||
|
}
|
||||||
|
|
||||||
|
get responseKey() {
|
||||||
|
return 'order';
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async create(data) {
|
||||||
|
const {
|
||||||
|
expiration,
|
||||||
|
request_type,
|
||||||
|
algorithm,
|
||||||
|
bit_length,
|
||||||
|
name,
|
||||||
|
payload_content_type,
|
||||||
|
mode,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
type: request_type,
|
||||||
|
meta: {
|
||||||
|
...(algorithm && { algorithm }),
|
||||||
|
...(bit_length && { bit_length }),
|
||||||
|
...(mode && { mode }),
|
||||||
|
...(payload_content_type && { payload_content_type }),
|
||||||
|
...(name && { name }),
|
||||||
|
...(expiration && { expiration }),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return this.client.create(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalOrdersStore = new OrdersStore();
|
||||||
|
export default globalOrdersStore;
|
@@ -43,14 +43,21 @@ export class SecretsStore extends Base {
|
|||||||
|
|
||||||
get mapper() {
|
get mapper() {
|
||||||
return (data) => {
|
return (data) => {
|
||||||
const { secret_ref, algorithm } = data;
|
const { secret_ref, algorithm, expiration } = data;
|
||||||
const [, uuid] = secret_ref.split('/secrets/');
|
const [, uuid] = secret_ref.split('/secrets/');
|
||||||
const { domain, expiration } = algorithm ? JSON.parse(algorithm) : {};
|
let extractedExpiration = expiration;
|
||||||
|
if (algorithm && algorithm.startsWith('{')) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(algorithm);
|
||||||
|
extractedExpiration = parsed.expiration || expiration;
|
||||||
|
} catch {
|
||||||
|
// Do nothing, Keep original expiration if parsing fails
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
id: uuid,
|
id: uuid,
|
||||||
domain,
|
expiration: extractedExpiration,
|
||||||
expiration,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -97,11 +104,58 @@ export class SecretsStore extends Base {
|
|||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
}
|
}
|
||||||
const [item, payload, listeners] = await Promise.all([
|
const [item, payload, listeners] = await Promise.all([
|
||||||
this.client.show(id, {}, { headers: { Accept: 'application/json' } }),
|
this.client.show(id, null, {
|
||||||
this.payloadClient.list(id, {}, { headers: { Accept: 'text/plain' } }),
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
this.payloadClient.list(id, null, {
|
||||||
|
headers: {
|
||||||
|
Accept: '*/*',
|
||||||
|
},
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
}),
|
||||||
globalListenerStore.fetchList(),
|
globalListenerStore.fetchList(),
|
||||||
]);
|
]);
|
||||||
item.payload = payload;
|
|
||||||
|
let decodedPayload = payload;
|
||||||
|
if (payload) {
|
||||||
|
try {
|
||||||
|
if (payload instanceof ArrayBuffer) {
|
||||||
|
const bytes = new Uint8Array(payload);
|
||||||
|
const contentType = item.payload_content_type || 'text/plain';
|
||||||
|
|
||||||
|
const textDecoder = new TextDecoder('utf-8', { fatal: false });
|
||||||
|
const decodedText = textDecoder.decode(bytes);
|
||||||
|
|
||||||
|
const isValidText = /^[\x20-\x7E\n\r\t]*$/.test(decodedText);
|
||||||
|
|
||||||
|
if (
|
||||||
|
isValidText &&
|
||||||
|
(contentType.includes('text') ||
|
||||||
|
contentType.includes('plain') ||
|
||||||
|
contentType.includes('json'))
|
||||||
|
) {
|
||||||
|
decodedPayload = decodedText;
|
||||||
|
} else {
|
||||||
|
let binary = '';
|
||||||
|
for (let i = 0; i < bytes.byteLength; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
decodedPayload = btoa(binary);
|
||||||
|
}
|
||||||
|
} else if (typeof payload === 'string') {
|
||||||
|
decodedPayload = payload;
|
||||||
|
} else {
|
||||||
|
decodedPayload = JSON.stringify(payload, null, 2);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error decoding payload:', error);
|
||||||
|
decodedPayload = 'Error decoding payload';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item.payload = decodedPayload;
|
||||||
// Determine if the certificate is used in the listener
|
// Determine if the certificate is used in the listener
|
||||||
this.updateItem(item, listeners);
|
this.updateItem(item, listeners);
|
||||||
const detail = this.mapper(item || {});
|
const detail = this.mapper(item || {});
|
||||||
@@ -124,15 +178,25 @@ export class SecretsStore extends Base {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
async create(data) {
|
async create(data) {
|
||||||
const { expiration, domain, algorithm, ...rest } = data;
|
const {
|
||||||
|
expiration,
|
||||||
|
secret_type,
|
||||||
|
algorithm,
|
||||||
|
bit_length,
|
||||||
|
mode,
|
||||||
|
payload_content_encoding,
|
||||||
|
...rest
|
||||||
|
} = data;
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
...rest,
|
...rest,
|
||||||
algorithm:
|
...(secret_type && secret_type.trim() !== '' && { secret_type }),
|
||||||
algorithm ||
|
...(algorithm && algorithm.trim() !== '' && { algorithm }),
|
||||||
JSON.stringify({
|
...(bit_length && { bit_length }),
|
||||||
domain,
|
...(mode && mode.trim() !== '' && { mode }),
|
||||||
expiration,
|
...(payload_content_encoding &&
|
||||||
}),
|
payload_content_encoding.trim() !== '' && { payload_content_encoding }),
|
||||||
|
...(expiration && { expiration }),
|
||||||
};
|
};
|
||||||
return this.client.create(body);
|
return this.client.create(body);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user