/**
 * Copyright 2021 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 crypto from "crypto" ; import { KubeConfig } from "@kubernetes/client-node" ; import { log , die , keyIDfromPEM } from "@opstrace/utils" ; import * as controllerconfig from "@opstrace/controller-config" ; import * as cli from "./index" ; import * as cryp from "./crypto" ; import * as util from "./util" ; export async function createKeypair ( ) { const modulusLengthBits = 2048 ; log . info ( "generate new RSA keypair. Modulus length (bits): %s" , modulusLengthBits ) ; const { privateKey } = crypto . generateKeyPairSync ( "rsa" , { modulusLength : modulusLengthBits } ) ; const privkeyPem = privateKey . export ( { type : "pkcs8" , format : "pem" } ) as string ; const fpath = cli . CLIARGS . tenantApiAuthenticatorKeyFilePath ; log . info ( "write key pair to file (mode: 600): %s" , fpath ) ; try { fs . writeFileSync ( fpath , privkeyPem , { mode : 0o600 , encoding : "utf8" } ) ; } catch ( err ) { return die ( `could not write file '${ fpath }': ${ err . message }` ) ; } } export async function addKeyToAuthenticatorConfig ( ) { const pubkeypem = cryp . readRSAPubKeyfromPEMfileAsPEMstring ( cli . CLIARGS . tenantApiAuthenticatorKeyFilePath ) ; const kubeconfig = await util . getKubeConfigForOpstraceClusterOrDie ( cli . CLIARGS . cloudProvider , cli . CLIARGS . instanceName ) ; await mutateClusterStateAddAuthenticatorKey ( kubeconfig , pubkeypem ) ; } export async function listKeys ( ) { const kubeconfig = await util . getKubeConfigForOpstraceClusterOrDie ( cli . CLIARGS . cloudProvider , cli . CLIARGS . instanceName ) ; await listAuthenticatorKeys ( kubeconfig ) ; } export async function removeKey ( ) { if ( cli . CLIARGS . keyId . length !== 40 ) { die ( "unexpected key ID: expecting a string with 40 characters" ) ; } const kubeconfig = await util . getKubeConfigForOpstraceClusterOrDie ( cli . CLIARGS . cloudProvider , cli . CLIARGS . instanceName ) ; await removeAuthenticatorKey ( kubeconfig , cli . CLIARGS . keyId ) ; } async function removeAuthenticatorKey ( kubeconfig , keyIdToRemove ) { const controllerConfigObj = await fetchControllerConfigWithCurrentKeySet ( kubeconfig ) ; const keySet = JSON . parse ( controllerConfigObj . tenant_api_authenticator_pubkey_set_json ) ; log . info ( "number of public keys currently configured: %s" , Object . keys ( keySet ) . length ) ; const newKeySetJSON = controllerconfig . authenticatorKeySetRemoveKey ( controllerConfigObj . tenant_api_authenticator_pubkey_set_json , keyIdToRemove ) ; await setControllerConfigWithNewKeySet ( kubeconfig , newKeySetJSON , controllerConfigObj ) ; } async function listAuthenticatorKeys ( kubeconfig ) { const controllerConfigObj = await fetchControllerConfigWithCurrentKeySet ( kubeconfig ) ; const keySet = JSON . parse ( controllerConfigObj . tenant_api_authenticator_pubkey_set_json ) ; log . info ( "number of public keys configured: %s" , Object . keys ( keySet ) . length ) ; for ( const keyId in keySet ) { process . stdout . write ( `${ keyId }\n` ) ; } } async function mutateClusterStateAddAuthenticatorKey ( kubeconfig , newPubkeyPem ) { const keyIDnewPubkey = keyIDfromPEM ( newPubkeyPem ) ; const controllerConfigObj = await fetchControllerConfigWithCurrentKeySet ( kubeconfig ) ; log . info ( "add new public key with id %s key to config" , keyIDnewPubkey ) ; const newKeySetJSON = controllerconfig . authenticatorKeySetAddKey ( controllerConfigObj . tenant_api_authenticator_pubkey_set_json , newPubkeyPem ) ; await setControllerConfigWithNewKeySet ( kubeconfig , newKeySetJSON , controllerConfigObj ) ; } async function setControllerConfigWithNewKeySet ( kubeconfig , newKeySetJSON , controllerConfigObj ) { log . info ( "new tenant_api_authenticator_pubkey_set_json, deserialized: %s" , JSON . parse ( newKeySetJSON ) ) ; controllerConfigObj . tenant_api_authenticator_pubkey_set_json = newKeySetJSON ; log . info ( "set new controller config (k8s config map)" ) ; await controllerconfig . set ( controllerConfigObj , kubeconfig ) ; log . info ( "controller config map updated" ) ; } async function fetchControllerConfigWithCurrentKeySet ( kubeconfig ) { const controllerConfigObj = await controllerconfig . fetch ( kubeconfig ) ; if ( controllerConfigObj === undefined ) { die ( "error: could not read current Opstrace controller config" ) ; } log . debug ( "current controller config: %s" , JSON . stringify ( controllerConfigObj , null , 2 ) ) ; if ( controllerConfigObj . tenant_api_authenticator_pubkey_set_json === undefined ) { die ( "error: parameter tenant_api_authenticator_pubkey_set_json is undefined in controller config" ) ; } log . debug ( "current tenant_api_authenticator_pubkey_set_json, deserialized: %s" , JSON . parse ( controllerConfigObj . tenant_api_authenticator_pubkey_set_json ) ) ; return controllerConfigObj ; }