diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..c3ac046f6 Binary files /dev/null and b/.DS_Store differ diff --git a/app/waf/src/components.ts b/app/waf/src/components.ts index 34927dc65..39123dc04 100644 --- a/app/waf/src/components.ts +++ b/app/waf/src/components.ts @@ -21,6 +21,7 @@ import { ComputeManagerV2, IdentityServiceProvider, AuthWithOSIdentity, + BarbicanManagerV1, } from './services'; import {NetworkDriver} from './services/network.service'; import {WafBindingKeys} from './keys'; @@ -76,10 +77,19 @@ export class OpenStackComponent implements Component { return await new ComputeManagerV2(this.application).bindComputeService(); }); + bindingBarbican = Binding.bind(WafBindingKeys.SecretManager).toDynamicValue( + async () => { + return await new BarbicanManagerV1( + this.application, + ).bindBarbicanService(); + }, + ); + bindings = [ this.bindingSolveAdminToken, this.bindingAuthMgr, this.bindingNetwork, this.bindingCompute, + this.bindingBarbican, ]; } diff --git a/app/waf/src/controllers/certificate.controller.ts b/app/waf/src/controllers/certificate.controller.ts new file mode 100644 index 000000000..193026889 --- /dev/null +++ b/app/waf/src/controllers/certificate.controller.ts @@ -0,0 +1,145 @@ +/** + * Copyright 2019 F5 Networks, Inc. + * + * 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 {Filter, repository} from '@loopback/repository'; +import { + post, + param, + get, + getFilterSchemaFor, + patch, + del, + requestBody, + HttpErrors, + RequestContext, + RestBindings, +} from '@loopback/rest'; +import {Certificate} from '../models'; +import {CertificateRepository} from '../repositories'; +import {Schema, Response, CollectionResponse} from '.'; +import {BaseController} from './base.controller'; +import {inject} from '@loopback/core'; + +const prefix = '/adcaas/v1'; +const createDesc: string = 'Certificate resource that need to be created'; +const updateDesc: string = + 'Certificate resource properties that need to be updated'; + +export class CertificateController extends BaseController { + constructor( + @repository(CertificateRepository) + public certificateRepository: CertificateRepository, + @inject(RestBindings.Http.CONTEXT) + protected reqCxt: RequestContext, + ) { + super(reqCxt); + } + + @post(prefix + '/certificates', { + responses: { + '200': Schema.response( + Certificate, + 'Successfully create Certificate resource', + ), + '400': Schema.badRequest('Invalid Certificate resource'), + '422': Schema.unprocessableEntity('Unprocessable Certificate resource'), + }, + }) + async create( + @requestBody(Schema.createRequest(Certificate, createDesc)) + reqBody: Partial, + ): Promise { + try { + reqBody.tenantId = await this.tenantId; + const data = await this.certificateRepository.create(reqBody); + return new Response(Certificate, data); + } catch (error) { + throw new HttpErrors.BadRequest(error.message); + } + } + + @get(prefix + '/certificates', { + responses: { + '200': Schema.collectionResponse( + Certificate, + 'Successfully retrieve Certificate resources', + ), + }, + }) + async find( + @param.query.object('filter', getFilterSchemaFor(Certificate)) + filter?: Filter, + ): Promise { + let data = await this.certificateRepository.find(filter, { + tenantId: await this.tenantId, + }); + return new CollectionResponse(Certificate, data); + } + + @get(prefix + '/certificates/{certificateId}', { + responses: { + responses: { + '200': Schema.response( + Certificate, + 'Successfully retrieve Certificate resource', + ), + '404': Schema.notFound('Can not find Certificate resource'), + }, + }, + }) + async findById( + @param(Schema.pathParameter('certificateId', 'Certificate resource ID')) + id: string, + ): Promise { + const data = await this.certificateRepository.findById(id, undefined, { + tenantId: await this.tenantId, + }); + return new Response(Certificate, data); + } + + @patch(prefix + '/certificates/{certificateId}', { + responses: { + '204': Schema.emptyResponse('Successfully update Certificate resource'), + '404': Schema.notFound('Can not find Certificate resource'), + }, + }) + async updateById( + @param(Schema.pathParameter('certificateId', 'Certificate resource ID')) + id: string, + @requestBody(Schema.updateRequest(Certificate, updateDesc)) + certificate: Certificate, + ): Promise { + console.log(id); + await this.certificateRepository.updateById(id, certificate, { + tenantId: await this.tenantId, + }); + } + + @del(prefix + '/certificates/{certificateId}', { + responses: { + '204': Schema.emptyResponse('Successfully delete Certificate resource'), + '404': Schema.notFound('Can not find Certificate resource'), + }, + }) + async deleteById( + @param(Schema.pathParameter('certificateId', 'Certificate resource ID')) + id: string, + ): Promise { + await this.certificateRepository.deleteById(id, { + tenantId: await this.tenantId, + }); + } +} diff --git a/app/waf/src/controllers/declaration.controller.ts b/app/waf/src/controllers/declaration.controller.ts index 9df756101..e362e4193 100644 --- a/app/waf/src/controllers/declaration.controller.ts +++ b/app/waf/src/controllers/declaration.controller.ts @@ -39,6 +39,8 @@ import { AS3DeployRequest, Adc, AS3Declaration, + TLSServerCertificate, + Certificate, } from '../models'; import {inject, CoreBindings} from '@loopback/core'; import { @@ -54,6 +56,8 @@ import { RuleRepository, ActionRepository, AdcRepository, + TLSserverRepository, + CertificateRepository, } from '../repositories'; import {BaseController, Schema, Response, CollectionResponse} from '.'; import {ASGManager, PortsUpdateParams} from '../services'; @@ -92,7 +96,10 @@ export class DeclarationController extends BaseController { public actionRepository: ActionRepository, @repository(AdcRepository) public adcRepository: AdcRepository, - //Suppress get injection binding exeption by using {optional: true} + @repository(TLSserverRepository) + public tlsserverRepository: TLSserverRepository, + @repository(CertificateRepository) + public certificateRepository: CertificateRepository, @inject(RestBindings.Http.CONTEXT, {optional: true}) protected reqCxt: RequestContext, @inject(CoreBindings.APPLICATION_INSTANCE) @@ -117,6 +124,15 @@ export class DeclarationController extends BaseController { await this.loadPool(service.defaultPool); } + if (service.serverTLS) { + service.serverTLSContent = await this.tlsserverRepository.findById( + service.serverTLS, + ); + if (service.serverTLSContent.certificates) { + await this.loadCertificateIndex(service.serverTLSContent.certificates); + } + } + let assocs = await this.serviceEndpointpolicyAssociationRepository.find({ where: { serviceId: service.id, @@ -137,6 +153,67 @@ export class DeclarationController extends BaseController { } } + private async loadCertificateIndex( + certificates: TLSServerCertificate[], + ): Promise { + for (let cert of certificates) { + const certList = await this.certificateRepository.find({ + where: { + id: cert.certificate, + }, + }); + + if (certList.length === 0) { + throw new HttpErrors.NotFound( + `Cannot find certificate ${cert.certificate}`, + ); + } + cert.certContent = certList[0]; + + await this.loadCertificateData(cert.certContent); + } + } + + private async loadCertificateData(certificate: Certificate) { + let userToken = await this.reqCxt.get(WafBindingKeys.Request.KeyUserToken); + let barbicanMgr = await this.wafapp.get(WafBindingKeys.SecretManager); + + if (certificate.certificate) { + certificate.certificate = await barbicanMgr.getSecret( + userToken, + certificate.certificate, + ); + } + + if (certificate.chainCA) { + certificate.chainCA = await barbicanMgr.getSecret( + userToken, + certificate.chainCA, + ); + } + + if (certificate.pkcs12) { + certificate.pkcs12 = await barbicanMgr.getSecret( + userToken, + certificate.pkcs12, + ); + } + + if (certificate.passphrase) { + certificate.passphrase = await barbicanMgr.getSecret( + userToken, + certificate.passphrase, + ); + } + + if (certificate.privateKey) { + certificate.privateKey = await barbicanMgr.getSecret( + userToken, + certificate.privateKey, + ); + } + } + private async loadPool(pool: Pool): Promise { pool.members = await this.poolRepository.members(pool.id).find(); diff --git a/app/waf/src/controllers/index.ts b/app/waf/src/controllers/index.ts index 59c8576f7..e3747bf2f 100644 --- a/app/waf/src/controllers/index.ts +++ b/app/waf/src/controllers/index.ts @@ -29,3 +29,5 @@ export * from './wafpolicy.controller'; export * from './monitor.controller'; export * from './membermonitorassc.controller'; export * from './poolmonitorassc.controller'; +export * from './certificate.controller'; +export * from './tlsserver.controller'; diff --git a/app/waf/src/controllers/tlsserver.controller.ts b/app/waf/src/controllers/tlsserver.controller.ts new file mode 100644 index 000000000..8f3cee06a --- /dev/null +++ b/app/waf/src/controllers/tlsserver.controller.ts @@ -0,0 +1,145 @@ +/** + * Copyright 2019 F5 Networks, Inc. + * + * 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 {Filter, repository} from '@loopback/repository'; +import { + post, + param, + get, + getFilterSchemaFor, + patch, + del, + requestBody, + HttpErrors, + RequestContext, + RestBindings, +} from '@loopback/rest'; +import {TLSServer} from '../models'; +import {TLSserverRepository} from '../repositories'; +import {Schema, Response, CollectionResponse} from '.'; +import {BaseController} from './base.controller'; +import {inject} from '@loopback/core'; + +const prefix = '/adcaas/v1'; +const createDesc: string = 'TLSServer resource that need to be created'; +const updateDesc: string = + 'TLSServer resource properties that need to be updated'; + +export class TLSServerController extends BaseController { + constructor( + @repository(TLSserverRepository) + public tlsserverRepository: TLSserverRepository, + @inject(RestBindings.Http.CONTEXT) + protected reqCxt: RequestContext, + ) { + super(reqCxt); + } + + @post(prefix + '/tlsservers', { + responses: { + '200': Schema.response( + TLSServer, + 'Successfully create TLSServer resource', + ), + '400': Schema.badRequest('Invalid TLSServer resource'), + '422': Schema.unprocessableEntity('Unprocessable TLSServer resource'), + }, + }) + async create( + @requestBody(Schema.createRequest(TLSServer, createDesc)) + reqBody: Partial, + ): Promise { + try { + reqBody.tenantId = await this.tenantId; + const data = await this.tlsserverRepository.create(reqBody); + return new Response(TLSServer, data); + } catch (error) { + throw new HttpErrors.BadRequest(error.message); + } + } + + @get(prefix + '/tlsservers', { + responses: { + '200': Schema.collectionResponse( + TLSServer, + 'Successfully retrieve TLSServer resources', + ), + }, + }) + async find( + @param.query.object('filter', getFilterSchemaFor(TLSServer)) + filter?: Filter, + ): Promise { + let data = await this.tlsserverRepository.find(filter, { + tenantId: await this.tenantId, + }); + return new CollectionResponse(TLSServer, data); + } + + @get(prefix + '/tlsservers/{tlsserverId}', { + responses: { + responses: { + '200': Schema.response( + TLSServer, + 'Successfully retrieve TLSServer resource', + ), + '404': Schema.notFound('Can not find TLSServer resource'), + }, + }, + }) + async findById( + @param(Schema.pathParameter('tlsserverId', 'TLSServer resource ID')) + id: string, + ): Promise { + const data = await this.tlsserverRepository.findById(id, undefined, { + tenantId: await this.tenantId, + }); + return new Response(TLSServer, data); + } + + @patch(prefix + '/tlsservers/{tlsserverId}', { + responses: { + '204': Schema.emptyResponse('Successfully update TLSServer resource'), + '404': Schema.notFound('Can not find TLSServer resource'), + }, + }) + async updateById( + @param(Schema.pathParameter('tlsserverId', 'TLSServer resource ID')) + id: string, + @requestBody(Schema.updateRequest(TLSServer, updateDesc)) + tlsserver: TLSServer, + ): Promise { + console.log(id); + await this.tlsserverRepository.updateById(id, tlsserver, { + tenantId: await this.tenantId, + }); + } + + @del(prefix + '/tlsservers/{tlsserverId}', { + responses: { + '204': Schema.emptyResponse('Successfully delete TLSServer resource'), + '404': Schema.notFound('Can not find TLSServer resource'), + }, + }) + async deleteById( + @param(Schema.pathParameter('tlsserverId', 'TLSServer resource ID')) + id: string, + ): Promise { + await this.tlsserverRepository.deleteById(id, { + tenantId: await this.tenantId, + }); + } +} diff --git a/app/waf/src/datasources/openstack.datasource.json b/app/waf/src/datasources/openstack.datasource.json index ff08c40fd..8760dc205 100644 --- a/app/waf/src/datasources/openstack.datasource.json +++ b/app/waf/src/datasources/openstack.datasource.json @@ -289,6 +289,25 @@ "headers" ] } + }, + { + "description": "get detail of a certificate.", + "template": { + "method": "GET", + "headers": { + "X-Auth-Token": "{userToken}", + "content-type": "application/json", + "Accept": "text/plain" + }, + "url": "{url}", + "responsePath": "$" + }, + "functions": { + "getSecretDetail": [ + "userToken", + "url" + ] + } } ] } diff --git a/app/waf/src/keys.ts b/app/waf/src/keys.ts index 98f936de1..014884fa4 100644 --- a/app/waf/src/keys.ts +++ b/app/waf/src/keys.ts @@ -21,6 +21,7 @@ import { AuthedToken, ComputeManager, NetworkDriver, + BarbicanManager, } from './services'; export interface LogFn { @@ -46,6 +47,10 @@ export namespace WafBindingKeys { 'services.openstack.ComputeManager', ); + export const SecretManager = BindingKey.create( + 'services.openstack.BarbicanManager', + ); + export const KeyNetworkDriver = BindingKey.create( 'services.openstack.NetworkDriver', ); diff --git a/app/waf/src/models/application.model.ts b/app/waf/src/models/application.model.ts index e08ed4209..aa8e025e3 100644 --- a/app/waf/src/models/application.model.ts +++ b/app/waf/src/models/application.model.ts @@ -16,6 +16,7 @@ import {CommonEntity, Declaration, AS3Declaration, Service} from '.'; import {model, property, hasMany} from '@loopback/repository'; +import {as3Name} from './as3.model'; @model() export class Application extends CommonEntity { @@ -87,6 +88,20 @@ export class Application extends CommonEntity { }); } + if (service.serverTLS && service.serverTLSContent) { + obj[ + as3Name(service.serverTLS) + ] = service.serverTLSContent.getAS3Declaration(); + + for (let cert of service.serverTLSContent.certificates) { + if (cert.certContent) { + obj[ + as3Name(cert.certContent.id) + ] = cert.certContent.getAS3Declaration(); + } + } + } + service.policies.forEach(policy => { obj[policy.getAS3Name()] = policy.getAS3Declaration(); }); diff --git a/app/waf/src/models/certificate.model.ts b/app/waf/src/models/certificate.model.ts new file mode 100644 index 000000000..e5451e7db --- /dev/null +++ b/app/waf/src/models/certificate.model.ts @@ -0,0 +1,141 @@ +/** + * Copyright 2019 F5 Networks, Inc. + * + * 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 {CommonEntity, AS3Declaration} from '.'; +import {model, property} from '@loopback/repository'; + +@model() +export class Certificate extends CommonEntity { + @property({ + type: 'string', + required: true, + schema: { + create: true, + update: true, + response: true, + // example: 'http://10.145.70.193:9311/v1/containers/d7f34226-69b8-4e25-ac51-ad4849653a8', + example: 'd7f34226-69b8-4e25-ac51-ad4849653a8', + }, + }) + certificate: string; + + @property({ + type: 'string', + required: false, + schema: { + create: true, + update: true, + response: true, + // example: 'http://10.145.70.193:9311/v1/containers/d7f34226-69b8-4e25-ac53-ad4849653a9', + example: 'd7f34226-69b8-4e25-ac53-ad4849653a9', + }, + }) + chainCA: string; + + @property({ + type: 'object', + required: false, + schema: { + create: true, + update: true, + response: true, + }, + }) + issuerCertificate: object; + + @property({ + type: 'string', + required: false, + schema: { + create: true, + update: true, + response: true, + }, + }) + passphrase: string; + + @property({ + type: 'string', + required: false, + schema: { + create: true, + update: true, + response: true, + // example: 'http://10.145.70.193:9311/v1/containers/d7f34226-69b8-4e25-ac56-ad4849653a0', + example: 'd7f34226-69b8-4e25-ac56-ad4849653a0', + }, + }) + pkcs12: string; + + @property({ + type: 'object', + required: false, + schema: { + create: true, + update: true, + response: true, + }, + }) + pkcs12Options: object; + + @property({ + type: 'string', + required: false, + schema: { + create: true, + update: true, + response: true, + // example: 'http://10.145.70.193:9311/v1/containers/d7f34226-69b8-4xxx-ac56-ad484965aaa', + example: 'd7f34226-69b8-4xxx-ac56-ad484965aaa', + }, + }) + privateKey: string; + + @property({ + type: 'object', + required: false, + schema: { + create: true, + update: true, + response: true, + }, + }) + staplerOCSP: object; + + constructor(data?: Partial) { + super(data); + this.as3Class = 'Certificate'; + } + + getAS3Declaration(): AS3Declaration { + let obj = super.getAS3Declaration(); + + obj.certificate = this.certificate; + obj.privateKey = this.privateKey; + + if (this.chainCA) { + obj.chainCA = this.chainCA; + } + + if (this.passphrase) { + obj.passphrase = { + ciphertext: Buffer.from(this.passphrase).toString('base64'), + }; + } + + return obj; + } +} diff --git a/app/waf/src/models/index.ts b/app/waf/src/models/index.ts index adbcf505e..4de784889 100644 --- a/app/waf/src/models/index.ts +++ b/app/waf/src/models/index.ts @@ -34,3 +34,5 @@ export * from './monitor.model'; export * from './membermonitor.model'; export * from './poolmonitorassoc.model'; export * from './wafpolicyondevice.model'; +export * from './certificate.model'; +export * from './tlsserver.model'; diff --git a/app/waf/src/models/service.model.ts b/app/waf/src/models/service.model.ts index c94b2adef..890baa86c 100644 --- a/app/waf/src/models/service.model.ts +++ b/app/waf/src/models/service.model.ts @@ -16,6 +16,7 @@ import {model, property} from '@loopback/repository'; import {CommonEntity, AS3Declaration, Pool, Endpointpolicy} from '.'; +import {TLSServer} from './tlsserver.model'; @model() export class Service extends CommonEntity { @@ -442,8 +443,12 @@ export class Service extends CommonEntity { type: 'string', required: false, schema: { - create: false, - update: false, + create: true, + update: true, + response: true, + }, + as3: { + type: 'name', }, }) serverTLS?: string; @@ -538,6 +543,9 @@ export class Service extends CommonEntity { defaultPool?: Pool; policies: Endpointpolicy[] = []; + // Do we really need to define a serverTLS here ? + serverTLSContent?: TLSServer; + // TODO: what's more, divide different types of Services into different model definition to avoid such long definition. //TODO: many-to-many relation to other objects @@ -559,6 +567,12 @@ export class Service extends CommonEntity { obj.policyEndpoint = this.policies.map(policy => policy.getAS3Name()); + // change default HTTP type to HTTPS + if (this.serverTLS) { + obj.type = 'HTTPS'; + obj.class = 'Service_HTTPS'; + } + return obj; } } diff --git a/app/waf/src/models/tlsserver.model.ts b/app/waf/src/models/tlsserver.model.ts new file mode 100644 index 000000000..d5747c384 --- /dev/null +++ b/app/waf/src/models/tlsserver.model.ts @@ -0,0 +1,223 @@ +/** + * Copyright 2019 F5 Networks, Inc. + * + * 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 {CommonEntity, AS3Declaration, Certificate} from '.'; +import {model, property} from '@loopback/repository'; +import {as3Name} from './as3.model'; + +@model() +export class TLSServer extends CommonEntity { + @property({ + type: 'boolean', + required: false, + default: false, + schema: { + create: false, + update: false, + response: true, + example: true, + }, + }) + allowExpiredCRL: boolean; + + @property({ + type: 'string', + required: false, + default: 'one-time', + schema: { + create: false, + update: false, + response: true, + example: 'one-time', + }, + }) + authenticationFrequency: string; + + @property({ + type: 'string', + required: false, + schema: { + create: false, + update: false, + response: true, + }, + }) + authenticationInviteCA: string; + + @property({ + type: 'string', + required: false, + default: 'ignore', + schema: { + create: false, + update: false, + response: true, + example: 'ignore', + }, + }) + authenticationMode: string; + + @property({ + type: 'string', + required: false, + schema: { + create: false, + update: false, + response: true, + }, + }) + authenticationTrustCA: string; + + @property({ + type: 'boolean', + required: false, + schema: { + create: false, + update: false, + response: true, + example: false, + }, + }) + c3dEnabled: false; + + @property({ + type: 'object', + required: false, + schema: { + create: false, + update: false, + response: true, + }, + }) + c3dOCSP: object; + + @property({ + type: 'string', + required: false, + default: 'drop', + schema: { + create: false, + update: false, + response: true, + example: 'drop', + }, + }) + c3dOCSPUnknownStatusAction: string; + + @property({ + type: 'array', + itemType: 'object', + required: false, + schema: { + create: true, + update: true, + response: true, + }, + }) + certificates: TLSServerCertificate[]; + + @property({ + type: 'string', + required: false, + default: 'DEFAULT', + schema: { + create: false, + update: false, + response: true, + example: 'DEFAULT', + }, + }) + ciphers: string; + + @property({ + type: 'object', + required: false, + schema: { + create: false, + update: false, + response: true, + }, + }) + crlFile: object; + + @property({ + type: 'string', + required: false, + schema: { + create: false, + update: false, + response: true, + example: 'none', + }, + }) + ldapStartTLS: string; + + @property({ + type: 'boolean', + required: false, + default: false, + schema: { + create: false, + update: false, + response: true, + example: false, + }, + }) + requireSNI: boolean; + + @property({ + type: 'boolean', + required: false, + default: false, + schema: { + create: false, + update: false, + response: true, + example: false, + }, + }) + staplerOCSPEnabled: boolean; + + constructor(data?: Partial) { + super(data); + this.as3Class = 'TLS_Server'; + } + + getAS3Declaration(): AS3Declaration { + let obj = super.getAS3Declaration(); + + let certificates = []; + for (let certIndex of this.certificates) { + let index: TLSServerCertificate = {}; + + if (certIndex.certificate) { + index.certificate = as3Name(certIndex.certificate); + } + if (certIndex.matchToSNI) { + index.matchToSNI = certIndex.matchToSNI; + } + certificates.push(index); + } + obj.certificates = certificates; + + return obj; + } +} +export interface TLSServerCertificate { + certificate?: string; + matchToSNI?: string; + certContent?: Certificate; +} diff --git a/app/waf/src/repositories/certificate.repository.ts b/app/waf/src/repositories/certificate.repository.ts new file mode 100644 index 000000000..73a5d4c90 --- /dev/null +++ b/app/waf/src/repositories/certificate.repository.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2019 F5 Networks, Inc. + * + * 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 {CommonRepository} from '.'; +import {Certificate} from '../models'; +import {DbDataSource} from '../datasources'; +import {inject} from '@loopback/core'; + +export class CertificateRepository extends CommonRepository< + Certificate, + typeof Certificate.prototype.id +> { + constructor(@inject('datasources.db') dataSource: DbDataSource) { + super(Certificate, dataSource); + } +} diff --git a/app/waf/src/repositories/index.ts b/app/waf/src/repositories/index.ts index 17ccd20ff..052fb3089 100644 --- a/app/waf/src/repositories/index.ts +++ b/app/waf/src/repositories/index.ts @@ -31,3 +31,5 @@ export * from './action.repository'; export * from './monitor.repository'; export * from './membermonitorassoc.repository'; export * from './poolmonitorassoc.repository'; +export * from './certificate.repository'; +export * from './tlsserver.repository'; diff --git a/app/waf/src/repositories/tlsserver.repository.ts b/app/waf/src/repositories/tlsserver.repository.ts new file mode 100644 index 000000000..6d27de393 --- /dev/null +++ b/app/waf/src/repositories/tlsserver.repository.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2019 F5 Networks, Inc. + * + * 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 {CommonRepository} from '.'; +import {TLSServer} from '../models'; +import {DbDataSource} from '../datasources'; +import {inject} from '@loopback/core'; + +export class TLSserverRepository extends CommonRepository< + TLSServer, + typeof TLSServer.prototype.id +> { + constructor(@inject('datasources.db') dataSource: DbDataSource) { + super(TLSServer, dataSource); + } +} diff --git a/app/waf/src/services/barbican.service.ts b/app/waf/src/services/barbican.service.ts new file mode 100644 index 000000000..021aefa81 --- /dev/null +++ b/app/waf/src/services/barbican.service.ts @@ -0,0 +1,90 @@ +/** + * Copyright 2019 F5 Networks, Inc. + * + * 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 {getService} from '@loopback/service-proxy'; +import {inject, Provider, CoreBindings} from '@loopback/core'; +import {OpenstackDataSource} from '../datasources'; +import {RestApplication} from '@loopback/rest'; +import {factory} from '../log4ts'; +import {WafBindingKeys} from '../keys'; + +export interface BarbicanService { + getSecretDetail(userToken: string, url: string): Promise; +} + +export class BarbicanServiceProvider implements Provider { + constructor( + // openstack must match the name property in the datasource json file + @inject('datasources.openstack') + protected dataSource: OpenstackDataSource = new OpenstackDataSource(), + ) {} + + value(): Promise { + return getService(this.dataSource); + } +} + +export abstract class BarbicanManager { + protected meta: {version: string} = {version: 'abstract'}; + + // @inject('services.BarbicanService') + protected barbicanService: BarbicanService; + + protected logger = factory.getLogger( + 'barbican.process.BarbicanManager.' + this.meta.version, + ); + + constructor( + @inject(CoreBindings.APPLICATION_INSTANCE) + protected application: RestApplication, + ) {} + + abstract getSecret(userToken: string, bbcId: string): Promise; + + async bindBarbicanService(): Promise { + // NOTE: define abstract member functions when we plan to do dynamic selection + // between compute v2 and v3. + // abstract createVirtualServer(): Promise; + // abstract queryVSState(serverId: string): Promise; + + // TODO: use bind/inject to use bindComputeService: + // @inject('services.bindComputeService') works differently within/outside + // of Controller. It doesn't work outside of Controller. So here we make + // call to Provider explicitly. + await new BarbicanServiceProvider().value().then(bbcServ => { + this.barbicanService = bbcServ; + }); + + return Promise.resolve(this); + } +} + +export class BarbicanManagerV1 extends BarbicanManager { + async getSecret(userToken: string, bbcId: string): Promise { + let adminToken = await this.application.get( + WafBindingKeys.KeySolvedAdminToken, + ); + + let url = adminToken.epBarbicanSecret(bbcId); + + const secretDetail = await this.barbicanService.getSecretDetail( + userToken, + url, + ); + + return JSON.parse(JSON.stringify(secretDetail))['body'][0]; + } +} diff --git a/app/waf/src/services/identity.service.ts b/app/waf/src/services/identity.service.ts index f72a09399..fe14ffa5f 100644 --- a/app/waf/src/services/identity.service.ts +++ b/app/waf/src/services/identity.service.ts @@ -456,6 +456,10 @@ export class AuthedToken { return this.endpointOf(this.interface, 'compute'); } + private epBarbican(): string { + return this.endpointOf(this.interface, 'key-manager'); + } + public epPorts(): string { return this.epNetwork() + '/v2.0/ports'; } @@ -468,6 +472,10 @@ export class AuthedToken { return this.epNetwork() + '/v2.0/floatingips'; } + public epBarbicanSecret(keyId: string): string { + return this.epBarbican() + `/v1/secrets/${keyId}`; + } + // TODO: Use user token's catalog instead of that of admin's. public epServers(tenantId: string) { let url = this.epCompute(); diff --git a/app/waf/src/services/index.ts b/app/waf/src/services/index.ts index 2846eb159..3ddc391a6 100644 --- a/app/waf/src/services/index.ts +++ b/app/waf/src/services/index.ts @@ -20,3 +20,4 @@ export * from './compute.service'; export * from './network.service'; export * from './do.service'; export * from './bigip.service'; +export * from './barbican.service'; diff --git a/deploy/appcluster.rc b/deploy/appcluster.rc index 2a5a93cde..829c6b723 100644 --- a/deploy/appcluster.rc +++ b/deploy/appcluster.rc @@ -2,10 +2,10 @@ WAF_APP_PORT=3000 WAF_APP_HOST=0.0.0.0 DATABASE_USER=postgres -DATABASE_PASSWORD=postgres +DATABASE_PASSWORD=Passw0rd DATABASE_DB=postgres DATABASE_PORT=5432 -DATABASE_HOST=postgres-server +DATABASE_HOST=127.0.0.1 DATABASE_DATA_DIRECTORY=../app/waf/data OS_AUTH_URL=http://10.250.13.69:5000/v3 diff --git a/deploy/package-lock.json b/deploy/package-lock.json new file mode 100644 index 000000000..48e341a09 --- /dev/null +++ b/deploy/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +}