/**
 * Copyright 2020 Opstrace, 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 fs from "fs" ; import { log , Dict , die , keyIDfromPEM , BUILD_INFO } from "@opstrace/utils" ; import { setAWSRegion } from "@opstrace/aws" ; import { GCPAuthOptions } from "@opstrace/gcp" ; import { LatestClusterConfigType , LatestAWSInfraConfigType , LatestGCPInfraConfigType , setClusterConfig , REGION_EKS_AMI_MAPPING } from "@opstrace/config" ; import { createCluster , setGcpProjectID , setCreateConfig , ClusterCreateConfigInterface } from "@opstrace/installer" ; import * as cli from "./index" ; import * as cryp from "./crypto" ; import * as ucc from "./ucc" ; import * as util from "./util" ; import * as schemas from "./schemas" ; import { OPSTRACE_DOCKERHUB_TOKEN , OPSTRACE_DOCKERHUB_USERNAME , logDockerHubCredentialsMessage } from "@opstrace/controller-config" ; type KeysetPemType = string ; type TenantApiTokensType = Dict < string > ; export async function create ( ) { const [ userClusterConfig , infraConfigAWS , infraConfigGCP ] = await ucc . uccGetAndValidate ( cli . CLIARGS . clusterConfigFilePath , cli . CLIARGS . cloudProvider ) ; const username = process . env [ OPSTRACE_DOCKERHUB_USERNAME ] ; const token = process . env [ OPSTRACE_DOCKERHUB_TOKEN ] ; if ( userClusterConfig . node_count > 5 ) { if ( ! ( username && token ) ) { die ( "OPSTRACE_DOCKERHUB_USERNAME, OPSTRACE_DOCKERHUB_TOKEN environment variables must be set to avoid image pull rate-limits from dockerhub (https://www.docker.com/increase-rate-limits)." + "OPSTRACE_DOCKERHUB_USERNAME must be your dockerhub username, and OPSTRACE_DOCKERHUB_TOKEN must be a valid dockerhub access token." + "You can create an access token here: https://hub.docker.com/settings/security" ) ; } } const [ tenant_api_authenticator_pubkey_set_json , tenantApiTokens ] = genCryptoMaterialForAPIAuth ( userClusterConfig ) ; if ( cli . CLIARGS . cloudProvider == "gcp" ) { const gcpopts = util . gcpValidateCredFileAndGetDetailOrError ( ) ; log . info ( "GCP project ID: %s" , gcpopts . projectId ) ; log . info ( "GCP service account email notation: %s" , gcpopts . credentials . client_email ) ; setGcpProjectID ( gcpopts . projectId ) ; } const renderedClusterConfig = { ... userClusterConfig , ... { aws : infraConfigAWS , gcp : infraConfigGCP , cloud_provider : cli . CLIARGS . cloudProvider , cluster_name : cli . CLIARGS . instanceName , tenant_api_authenticator_pubkey_set_json : tenant_api_authenticator_pubkey_set_json } } ; log . info ( "rendered cluster config:\n%s" , JSON . stringify ( renderedClusterConfig , null , 2 ) ) ; schemas . LatestRenderedClusterConfigSchema . validateSync ( renderedClusterConfig , { strict : true } ) ; setClusterConfig ( renderedClusterConfig ) ; const createConfig = { holdController : cli . CLIARGS . holdController , tenantApiTokens : tenantApiTokens , kubeconfigFilePath : cli . CLIARGS . kubeconfigFilePath } ; setCreateConfig ( createConfig ) ; if ( infraConfigAWS !== undefined ) { if ( ! ( infraConfigAWS . region in REGION_EKS_AMI_MAPPING ) ) { die ( `The AWS region you have configured is not (yet) supported: ${ infraConfigAWS . region }` ) ; } setAWSRegion ( infraConfigAWS . region ) ; } logDockerHubCredentialsMessage ( "create" ) ; await promptForResourceCreation ( renderedClusterConfig ) ; writeTenantApiTokenFiles ( tenantApiTokens ) ; await createCluster ( util . smErrorLastResort ) ; } async function promptForResourceCreation ( ccfg ) { if ( ccfg . cloud_provider === "aws" ) { const url = `https://go.opstrace.com/cli-aws-mutating-api-calls/${ BUILD_INFO . VERSION_STRING }` ; log . info ( "Before we continue, please review the set of state-mutating " + "AWS API calls emitted by this CLI during cluster creation: %s" , url ) ; await util . promptForProceed ( ) ; } } function writeTenantApiTokenFiles ( tenantApiTokens ) { for ( const [ tname , token ] of Object . entries ( tenantApiTokens ) ) { const fpath = `tenant-api-token-${ tname }` ; log . info ( "write api token for tenant %s to file %s" , tname , fpath ) ; fs . writeFileSync ( fpath , token , { encoding : "utf8" } ) ; } } function genCryptoMaterialForAPIAuth ( ucc ) { const tenantApiTokens = { } ; let tenant_api_authenticator_pubkey_set_json = "" ; if ( ! ucc . data_api_authentication_disabled ) { const tnames = [ ... ucc . tenants ] ; tnames . push ( "system" ) ; for ( const tenantName of tnames ) { const t = cryp . generateJWTforTenantAPI ( tenantName , cli . CLIARGS . instanceName ) ; tenantApiTokens [ tenantName ] = t ; } const data_api_authn_pubkey_pem = cryp . getPubkeyAsPem ( ) ; const keyId = keyIDfromPEM ( data_api_authn_pubkey_pem ) ; log . info ( "serialized public key (id: %s) for tenant API token verification" , keyId ) ; const keyset = { [ keyId ] : data_api_authn_pubkey_pem } ; tenant_api_authenticator_pubkey_set_json = JSON . stringify ( keyset ) ; log . debug ( "public key (id: %s) in PEM-encoded X.509 SubjectPublicKeyInfo/OpenSSL format:\n%s" , keyId , data_api_authn_pubkey_pem ) ; } else { log . info ( "Do not generate cryptographic material (tenant API authentication is disabled)" ) ; } return [ tenant_api_authenticator_pubkey_set_json , tenantApiTokens ] ; }