Pulumi: Manipulate connection string during deployment - azure

I'm using Pulumi to deploy several Azure ressources, which works fine.
I'm deploying a TopicAuthorizationRule and I need to manipulate the connection string in order to have it working with an Azure Function Trigger.
const myPolicy = new azure.eventhub.TopicAuthorizationRule(...);
const myPolicyConnectionString = myPolicy.primaryConnectionString.get();
const goodConnectionString = myPolicyConnectionString .substr(0, myPolicyConnectionString .lastIndexOf(';EntityPath'));
And I have this error: Cannot call '.get' during update or preview
How can I do this string manipulation in order to set it in AppSettings?

Connection string value is unknown yet at the time of preview, so you can't use it directly. It's contained in a value of type Output<T> which is going to be resolved at update time.
You can transform the values of Output<T> by using apply function:
const goodConnectionString =
myPolicy.primaryConnectionString.apply(s => s.substr(0, s.lastIndexOf(';EntityPath'));
which can then be used to assign AppSettings (without calling get explicitly).

Related

Is it safe to store public keys/policies in a node.js constant in Lambda

I am writing a AWS lambda Authorizer in node.js. We are required to call Azure AD API to fetch the public keys/security policies to validate the incoming the Access Token.
However, to optimize the performance, I decided to store the public keys/security policies in node.js as a constant (this will be active until the Lambda is running or TTL of the keys expire).
Question : Is it safe from a security perspective ? I want to avoid "caching" it in DynamoDB as calls to DynamoDB would also incur additional milliseconds. Ours is a very high traffic application and we would like to save any millisecond possible for optimal performance. Also, any best practice is also higly appreciated
Typically, you should not hard-code things like that in your code. Even though it is not a security problem, it is making maintenance harder.
For example: when the key is "rotated" or the policy changed and you had it hard-coded in your Lambda, you would need to update your code and do another deployment. This is often causing issues, because the developer forgot about this etc. causing issues because your authorizer does not work anymore. If the Lambda loads the information from an external service like S3, SSM or directly Azure AD, you don't need another deployment. In theory, it should sort itself out depending on which service you use and how you manage your keys etc.
I think the best way is to load the key from an external service during the initialisation phase of the Lambda. That means when it is "booted" for the first time and then cache that value for the duration of the Lambdas lifetime (a few minutes to a few hours).
You could for example load the public keys and policies either directly from Azure, from S3 or SSM Parameter Store.
The following code uses the AWS SDK NodeJS v3, which is not bundled with the Lambda Runtime. You can use v2 of the SDK as well.
const { SSMClient, GetParameterCommand } = require("#aws-sdk/client-ssm");
// This only happens once, when the Lambda is started for the first time:
const init = async () => {
const config = {}
try {
// use whatever 'paramName' you defined, when you created the SSM parameter
const paramName = "/azure/publickey"
const command = new GetParameterCommand({Name: paramName});
const ssm = new SSMClient();
const data = await ssm.send(command);
config["publickey"] = data.Parameter.Value;
} catch (error) {
return Promise.reject(new Error("unable to read SSM parameter '"+ paramName + "'."));
}
return new Promise((resolve, reject) => {
resolve(config);
reject(new Error("unable to create configuration. Unknown error."));
});
};
const initPromise = init();
exports.handler = async (event) => {
const config = await initPromise;
console.log("My public key '%s'", config.key);
return "Hello World";
};
The most important point of this code is the init "function", which is only run on once, creating a "config" which should contain your AWS SDK clients and all the configuration you need in your code. This way, you don't have to get the policy for every request that the Lambda is processing etc.

Firebase function, increment a value

I have a firebase cloud function that should increment the value of a field when a new document is created. The function executes successfully as I can see this within the firebase logs, but it doesn't increment the value of the field.
exports.onFileAdded = functions.firestore.document("files/{id}").onCreate(async (change, context) => {
const file = change.data();
const hub = await getByCollectionAndId('hubs', file.hubId);
Firebase.firestore().collection('teams').doc(hub.teamId).set({tileCount: Firebase.database.ServerValue.increment(1)}, {merge: true});
});
As there are no errors, and the function executes successfully, what am I doing wrong?
The problem is:
Firebase.database.ServerValue.increment(1)
You're using the operator to increment a value on the Realtime Database, but you are using it on Cloud Firestore. While both databases are part of Firebase, they're completely separate, and the API for one doesn't apply to the other.
To fix the problem, use the increment operator for Firestore:
firebase.firestore.FieldValue.increment(1)
Also see my answer here: How to increment existing number field in Cloud Firestore

How to update DocumentReference?

I tried to update DocumentReference but could not do it.
update() method fails. How to use it? (How to pass argument?)
firebase-admin version is 6.3.0.
#google-cloud/firestore version is 0.19.0.
❯ firebase functions:shell
i functions: Preparing to emulate functions.
Warning: You're using Node.js v8.14.0 but Google Cloud Functions only supports v6.11.5.
✔ functions: sampleFunc
firebase > const admin = require('firebase-admin');
firebase > admin.initializeApp();
firebase > let ref = admin.firestore().collection("users").doc('edqupYQhzqV1ODjEpoJn');
firebase > let updates = { email: 'xxx#yyy.zzz' };
firebase > ref.update(updates).then(value => console.log(value) );
Error: Update() requires either a single JavaScript object or an alternating list of field/value pairs that can be followed by an optional precondition. Argument "dataOrField" is not a valid Document. Input is not a plain JavaScript object.
at WriteBatch.update (/Users/xxx/Desktop/sample-functions/functions/node_modules/#google-cloud/firestore/build/src/write-batch.js:359:23)
at DocumentReference.update (/Users/xxx/Desktop/sample-functions/functions/node_modules/#google-cloud/firestore/build/src/reference.js:387:14)
Update
Document has already been created, so get() works.
firebase > ref.get().then(snapshot => console.log(snapshot.data()));
Errors also occur in set() as well.
firebase > ref.set({email: 'aaa#bbb.ccc'}, {merge: true}).then(value => console.log(value));
Error: Argument "data" is not a valid Document. Input is not a plain JavaScript object.
at Validator.(anonymous function).values [as isDocument] (/Users/xxx/Desktop/sample-functions/functions/node_modules/#google-cloud/firestore/build/src/validate.js:99:27)
at WriteBatch.set (/Users/xxx/Desktop/sample-functions/functions/node_modules/#google-cloud/firestore/build/src/write-batch.js:232:25)
at DocumentReference.set (/Users/xxx/Desktop/sample-functions/functions/node_modules/#google-cloud/firestore/build/src/reference.js:349:27)
Following the example you posted, you are trying to update a document that does not exist. In this case you should create it first.
If you're not sure whether the document exists, pass the option to merge the new data with any existing document to avoid overwriting entire documents.
From Google's Documentation:
var cityRef = db.collection('cities').doc('BJ');
var setWithOptions = cityRef.set({
capital: true
}, {merge: true});
Reference: Firestore Add Data
I've never tried using the Admin SDK in the functions:shell like you do in your example. However I can easily reproduce your error. I guess that the functions:shell somehow tampers with your variable updates. When i log updates undefined is concatenated in output
firebase > console.log(updates)
{ email2: 'xxx#yyy.zzz' }
undefined
If you put your code in a JS file, initialize your app with a service account and run in with node it will most likely work just fine!
Read more about how to use Service Account
create an object first then assign your values to a field/property on that object like:
var myObj = {};
myObj["myfield"] = myvalue;
now pass as the 2nd argument of .set or .update.

How can I access the data set before the most recently posted ones in firebase

Currently I have a code for firebase cloud functions that is able to read the latest data set coming in using the onWrite function. However, I wish to be able to read the data set directly before the latest data set (as shown in the picture where my latest would for EG be LHYfA9GkpwOMS0OysUd and the previous data set would be LHYF9jlBmlQL5qD19-D). Is there a way to do this with firebase cloud functions? The code below shows how I get the latest data and edit it! I want to use the latest data's etotal value to subtract that from the previous data's etotal value.
edit: my data values wont be replacing the previous set so I cannot use 'previous'
exports.editData = functions.database.ref('/AllData/immaphoton/A/{id}').onWrite((change, context) => {
const afterData = change.after;
if (afterData.exists()) {
//console.log('hey');
const data = afterData.val();
// set of data to multiply by turns ratio
var actualEIn = (data.ein)*200;
var actualEOut = (data.eout)*200;
var totalE = (actualEIn - actualEOut);
var actualTotalPower = (data.tp)*200;
var ISOts = (data.ts);
// add timezone offset to milliseconds
var localTS = moment(Date.parse(data.ts) + (8*1000*60*60)).format('YYYY-MM-DD');
// need to change ts to suit each type of data
}
return admin.database().ref('/editedData/immaphoton/A').push({
ein: actualEIn,
eout: actualEOut,
etotal: totalE,
tp: actualTotalPower,
timestamp: ISOts,
localtime: localTS,
});
});
If you know the key of the new child node, you can read the item before it by combining orderByKey().limitToLast():
var key = context.params.id; // key of new/updated child
var ref = admin.database().ref('/editedData/immaphoton/A');
var query = ref.orderByKey().endAt(key).limitToLast(2);
query.once("child_added").then(function(snapshot) {
console.log(snapshot.key);
});
This code is not dependent on running in Cloud Functions, except for how it determines the value of key. If you can determine the key another way, this would run with any of the Firebase JavaScript SDKs.

DocumentDB connection string

Azure application settings (for azure function) has a option for a DocumentDB connection string
Anyone have any idea how this should be populated/formatted?
i currently use:
var documentDbEndpointUri = new Uri(ConfigurationManager.AppSettings["DocumentDbEndpointUri"]);
var documentDbAuthKey = ConfigurationManager.AppSettings["DocumentDbAuthKey"];
return new DocumentClient(documentDbEndpointUri, documentDbAuthKey);
Although I'd like to switch to a single value connection string.
Try AccountEndpoint=https://accountname.documents.azure.com:443/‌​;AccountKey=accountk‌​ey==;Database=database
Firstly, as #Gaurav Mantri said in comment, currently DocumentClient does not have constructor overloads using connection string, you cannot directly use a connection string to create an instance of DocumentClient even if you provide/add connection string for DocumentDB in Azure application settings.
Note: here is a feedback for this issue, if you have same feature request, you can vote for it.
Secondly, If you’d like to access the DocumentDB service via DocumentClient, you can add both DocumentDbEndpointUri and DocumentDbAuthKey in App settings, and then read them in function code.
var serviceEndpoint = System.Configuration.ConfigurationManager.AppSettings["DocumentDbEndpointUri"];
var authKey = System.Configuration.ConfigurationManager.AppSettings["DocumentDbAuthKey"];
//or
//var serviceEndpoint = Environment.GetEnvironmentVariable("DocumentDbEndpointUri");
//var authKey = Environment.GetEnvironmentVariable("DocumentDbAuthKey");

Resources