237 lines
9.9 KiB
Plaintext
237 lines
9.9 KiB
Plaintext
<cfcomponent extends="taffy.core.resource" taffy:uri="/instances/{instanceUid}/vault/{secretName}" hint="getting secrets from the vault">
|
|
|
|
<cfsilent>
|
|
<cfimport prefix="m" taglib="../lib"/>
|
|
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
|
|
</cfsilent>
|
|
|
|
<cffunction name="get" hint="Получить секрет">
|
|
<cfargument name="instanceUid" type="string" required=true hint="type:UUID"/>
|
|
<cfargument name="secretName" type="string" required=true hint="type:string"/>
|
|
<!--- <cfargument name="usrId" type="string" hint="type:integer; no need to provide this argument in request, it is injected by IAM"/> --->
|
|
<!--- arguments.usrId неявно вбрасывается из Application.cfm --->
|
|
|
|
<cftry>
|
|
<cfset this.helper.validateField(arguments, "instanceUid", "guid")/>
|
|
<cfcatch type="invalidParamValue">
|
|
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
<cfset var local={}/>
|
|
<cfset local.vaultData={}/>
|
|
|
|
<!--- <cfset logVaultAccess(
|
|
"#request.iam_service_url#/events",
|
|
request.auth_header,
|
|
request.stand,
|
|
arguments.instanceUid,
|
|
arguments.secretName,
|
|
"",
|
|
arguments.login,
|
|
arguments.clientID,
|
|
arguments.usrId,
|
|
arguments.contragentId,
|
|
arguments.isImpersonated)/> --->
|
|
|
|
<!--- *** Добавить проверку, что секрет перечислен в fields - стало быть, доступен пользователю --->
|
|
|
|
<!--- тут необходимо проконтролировать, что мы смотрим не чужие данные --->
|
|
<!--- в других местах мы молча возврашаем пустой резалтсет на запрос чужих данных --->
|
|
<!--- варианты: 403, 404, пустой набор данных --->
|
|
<cfquery name="local.qCurrentState" result="local.result">
|
|
select
|
|
<m:field_set titleMapOut="local.currentStateTitleMap" lengthOut="local.fieldCount">
|
|
<m:field title="instance_state_uid">st.instance_state_uid::text as instance_state_uid</m:field>
|
|
<m:field title="v.">st.version</m:field>
|
|
<m:field title="creator_id">st.creator_id</m:field>
|
|
<m:field title="dt_state">to_char(st.dt_state, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_state</m:field>
|
|
<m:field formatter=#request.castToBool#>st.is_test</m:field>
|
|
<m:field title="instance_operation_uid">st.instance_operation_uid::text as instance_operation_uid</m:field>
|
|
<m:field title="instance_data" formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field>
|
|
<m:field formatter=#function(x){return (x GT 0)}#>(st.instance_data->>'isDeleted')::boolean as is_deleted</m:field>
|
|
</m:field_set>
|
|
from instance_state st
|
|
join instance e on (st.instance_uid=e.instance_uid)
|
|
join specification_item i on (e.specification_item_id=i.specification_item_id)
|
|
where st.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#"/>
|
|
AND i.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/><!--- access protection --->
|
|
order by st.version desc
|
|
limit 1
|
|
</cfquery>
|
|
|
|
<cfif local.qCurrentState.recordCount EQ 0>
|
|
<cfreturn representationOf("Cannot find instance current state or no permission to view").withStatus(404)/>
|
|
</cfif>
|
|
|
|
<cfif len(local.qCurrentState.instance_data) EQ 0>
|
|
<cfreturn representationOf("Instance current state is empty").withStatus(404)/>
|
|
</cfif>
|
|
|
|
<!--- <cfdump var=#local.qCurrentState#/><cfabort/> --->
|
|
<cfset var vaultUrl=""/>
|
|
<cftry>
|
|
<cfset var instanceData=#deserializeJson(local.qCurrentState.instance_data)#/>
|
|
<cfif structKeyExists(instanceData,"vaultUrl")>
|
|
<cfset vaultUrl=instanceData.vaultUrl/> <!--- v001 --->
|
|
<cfelseif structKeyExists(instanceData,"vault")>
|
|
<cfif !structKeyExists(instanceData.vault,"Url")>
|
|
<cfreturn representationOf("instanceData.vault does not contain url").withStatus(422)/>
|
|
</cfif>
|
|
<cfset vaultUrl=instanceData.vault.Url/><!--- v002 --->
|
|
<cfelse>
|
|
<cfreturn representationOf("Instance data do not contain vault url").withStatus(422)/>
|
|
</cfif>
|
|
|
|
<cfhttp method="post"
|
|
url="#request.vault_login_url#"
|
|
timeout="3"
|
|
charset="utf-8"
|
|
result="local.res"
|
|
>
|
|
<cfhttpparam type="HEADER" name="User-Agent" value="#request.USER_AGENT#"/>
|
|
<cfhttpparam type="BODY" value='{"role_id":"#request.vault_role_id#","secret_id":"#request.vault_secret_id#"}'/>
|
|
</cfhttp>
|
|
|
|
<cfif local.res.status_code NEQ 200>
|
|
<cfreturn representationOf(local.res).withStatus(422)/>
|
|
</cfif>
|
|
|
|
<cfset resData=#deserializeJson(local.res.filecontent)#/>
|
|
<cfset client_token=#resData.auth.client_token#/>
|
|
|
|
<cftry>
|
|
<cfhttp method="get"
|
|
url="#vaultUrl#"
|
|
timeout="3"
|
|
charset="utf-8"
|
|
result="local.cred">
|
|
<cfhttpparam type="HEADER" name="X-Vault-Token" value="#client_token#">
|
|
<cfhttpparam type="HEADER" name="User-Agent" value="#request.USER_AGENT#"/>
|
|
</cfhttp>
|
|
<cfif local.cred.status_code NEQ 200>
|
|
<cfreturn representationOf(local.cred).withStatus(422)/>
|
|
</cfif>
|
|
<!--- <cfdump var=#cred#/><cfabort/> --->
|
|
<!--- *** костыль для перехода от формата v001 к v002 --->
|
|
<cfif structKeyExists(instanceData,"vault")
|
|
AND structKeyExists(instanceData.vault,"fields")
|
|
AND arrayFind(instanceData.vault.fields, arguments.secretName)>
|
|
<cfset "local.tempVaultData" = #deserializeJson(local.cred.filecontent).data.data#/>
|
|
<cfif !structKeyExists(local.tempVaultData, arguments.secretName)>
|
|
<cfreturn representationOf("Secret is declared but does not exist").withStatus(404)/>
|
|
</cfif>
|
|
<cfset "local.vaultData" = local.tempVaultData[arguments.secretName]/>
|
|
<cfelse>
|
|
<cfreturn representationOf("Secret not found or no permission to view").withStatus(404)/>
|
|
</cfif>
|
|
|
|
|
|
<cfcatch type="ANY">
|
|
<cfreturn representationOf(cfcatch).withStatus(500)/>
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
<cfcatch type="ANY">
|
|
<cfreturn representationOf(cfcatch).withStatus(500)/>
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
<!--- логируем успешную попытку доступа. (если логировать сначала - неизвестен результат. *** Можно в catch логировать неуспешный --->
|
|
<cfset logVaultAccess(
|
|
"#request.iam_service_url#/events",
|
|
request.auth_header,
|
|
request.stand,
|
|
arguments.instanceUid,
|
|
arguments.secretName,
|
|
vaultUrl,
|
|
arguments.login,
|
|
arguments.clientID,
|
|
arguments.usrId,
|
|
arguments.contragentId,
|
|
arguments.isImpersonated)/>
|
|
|
|
<cfset var out = structNew("linked")/>
|
|
<cfset "out.name" = #arguments.secretName#/>
|
|
<cfset "out.value" = #isSimpleValue(local.vaultData)? local.vaultData : serializeJson(local.vaultData)#/><!--- cast any to string --->
|
|
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
|
|
<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
|
|
<cfreturn representationOf(out) />
|
|
</cffunction>
|
|
|
|
<cffunction name="logVaultAccess" returntype="void">
|
|
<cfargument name="loggerUrl" type="string" required=true hint=""/>
|
|
<cfargument name="authHeader" type="string" required=true hint="type:string"/>
|
|
<cfargument name="stand" type="string" required=true hint="type:string"/>
|
|
<cfargument name="instanceUid" type="string" required=true hint="type:UUID"/>
|
|
<cfargument name="secretName" type="string" required=true hint="type:string"/>
|
|
<cfargument name="vaultUrl" type="string" required=true hint="type:string"/>
|
|
<cfargument name="login" type="string" required=true hint="type:string"/>
|
|
<cfargument name="wz" type="string" required=true hint="type:string WZ"/>
|
|
<cfargument name="usrId" type="string" required=true hint="type:string WZ"/>
|
|
<cfargument name="contragentId" type="string" required=true hint="type:string WZ"/>
|
|
<cfargument name="isImpersonated" type="string" required=true hint="type:boolean"/>
|
|
|
|
<cftry>
|
|
<cfset var local={}/>
|
|
<!--- расшифруем некоторые данные пользователя для читабельности --->
|
|
<!--- нужно еще учесть имперсонирование --->
|
|
<!--- <cfquery name="local.qUsr" result="local.result">
|
|
select
|
|
u.usr_id
|
|
u.login
|
|
u.email
|
|
k.contragent_id
|
|
k.external_uid::text as external_uid
|
|
k.contragent
|
|
from usr u
|
|
left outer join contragent k on (u.contragent_id=k.contragent_id)
|
|
where u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#ARGUMENTS.usrId#/>
|
|
</cfquery> --->
|
|
|
|
|
|
<cfset var msg = {
|
|
"event_type": "credentials_access",
|
|
"ip_address": "#CGI.REMOTE_ADDR#",
|
|
"user_agent": "#CGI.HTTP_USER_AGENT#",
|
|
"metadata": {
|
|
"credentials_type": "instance_secret",
|
|
"vault_url": "#arguments.vaultUrl#",
|
|
"secretName": "#arguments.secretName#",
|
|
"action": "view",
|
|
"instance_uid": "#arguments.instanceUid#",
|
|
"environment": "#arguments.stand#",
|
|
"usr_id": "#arguments.usrId#",
|
|
"contragent_id": "#arguments.contragentId#",
|
|
"login": "#arguments.login#",
|
|
"wz": "#arguments.wz#",
|
|
"isImpersonated": "#arguments.isImpersonated#"
|
|
}
|
|
}/>
|
|
<!--- <cfdump var=#msg#/><cfabort/> --->
|
|
|
|
<cfhttp method="post"
|
|
url="#arguments.loggerUrl#"
|
|
timeout="3"
|
|
charset="utf-8"
|
|
result="local.res"
|
|
>
|
|
<cfhttpparam type="HEADER" name="Authorization" value="#arguments.authHeader#">
|
|
<cfhttpparam type="HEADER" name="Content-Type" value="application/json"/>
|
|
<cfhttpparam type="BODY" value="#serializeJson(msg)#"/>
|
|
</cfhttp>
|
|
|
|
<!---
|
|
arguments:<cfdump var=#arguments#/>
|
|
msg:<cfdump var=#msg#/>
|
|
local.res:<cfdump var=#local.res#/>
|
|
<cfabort/>
|
|
--->
|
|
<cfcatch type="any">
|
|
<cfrethrow/><!--- на этапе отладки лучше тут падать --->
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
</cffunction>
|
|
|
|
</cfcomponent> |