Can I access Google Secrets Manager secrets externally - authentication

I am writing an app that I will be hosting in Google Cloud Functions with some config stored in Secrets Manager. I would like to share this information with another node app that is running on my local machine. Is this possible? I have tried using the npm package but I can’t figure out how I can authenticate to get access to the manager.
I am using a service key to access firestore:
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
const service_key = {
apiKey: myKey,
authDomain: "my-poroject.firebaseapp.com",
projectId: "my-poroject",
storageBucket: "my-poroject.appspot.com",
messagingSenderId: "0123456789",
appId: "0:00000000:web:00000000000000"
}
const app = initializeApp(service_Key);
export const db = getFirestore(app);
This all works perfectly, but I can't see how I would apply the key or 'app' when using secret manager:
const {SecretManagerServiceClient} = require('#google-cloud/secret-manager');
const client = new SecretManagerServiceClient();

As public cloud provider, most of the Google Cloud services are publicly accessible. So YES, you can access the secret from outside.
However, you must have the required credentials and permissions to access the secrets.
You can use a service account key file, which is also a secret (and I never recommend that option, but in some cases, it's useful), to generate an access token and to query safely secret manager. The problem is the service account key file, it's a secret to protect secret... The security level depends on your external platform.
You can also have a look to Identity Federation Pool that can help you to use your already known identity and to be transparently authenticated on Google Cloud. It's very powerful and you no longer need secret on your side and you increase your security posture.

Related

Firebase functions auth.generateEmailVerificationLink() generating link with wrong apiKey

I have a Firebase functions project with dev and prod versions. There I'm using auth.generateEmailVerificationLink() to send email verification for a newly created user. Everything works well except in prod environment (testing locally or hosted) the apiKey in the link generated by auth.generateEmailVerificationLink() is not same as Firebase's default apiKey. And clicking that link I get the page with error code:
Try verifying your email again
Your request to verify your email has expired or the link has already been used
Note that when I get the link with the wrong apiKey, if I change it to the right apiKey. the verification works. So it seems the whole problem is related to the wrong apiKey in generated email verification link.
Also to note that the wrong apiKey is not random key but used in project front end for Google Maps apis.
The code itself is simple. (I'm leaving out code which creates user etc as those parts all work perfectly)
-Initializing Admin SDK:
import { initializeApp } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
initializeApp();
const auth = getAuth();
export { auth };
-Generating email verification email
const sendEmail = async () => {
const actionCodeSettings = {
// This url is working correctly, it is the same as in Firebase console
// and when changing the wrong apiKey to correct redirecting works correctly
url: process.env.DOMAIN as string,
};
await auth
.generateEmailVerificationLink(email, actionCodeSettings) // email is the email of newly created user
.then((link) => {
// generate email message with link
// generate mailOptions
// use transporter to send email
});
};
Thank you for any help
EDIT
I tested deleting that "wrong" apiKey from GCP credentials page and replaced it with another. Then running the function locally everything worked normally but the "wrong" is still in the verification email link even tho it doesn't exist anymore.
Firebase strongly recommends that if Admin SDK is used in Cloud Functions, among others, initializing the app should be done without parameters.
https://firebase.google.com/docs/admin/setup#initialize-without-parameters
For me it seems something is for some reason pulling that "wrong" and now even deleted apiKey from somewhere to usage.
I solved this by noticing that, unlike in dev project, Web Api Key (Project Settings>General) is different than Web App's firebaseConfig apiKey.
So I added correct permission to this Web Api Key (Identity Toolkit API is required for email verification email) found in GCP credentials and now the cloud function sends email verification emails with correct and working apiKey.

How do I add Google Application Credentials/Secret to Vercel Deployment?

I am using Vercel Deployments with a NextJS app. Deployments are automatically run when I push to master, but I don't want to store keys in GitHub. My serverless functions are reliant on my database. When run locally, I can simply use Google's Default Authentication Credentials, but this is not possible when deployed to Vercel. As such, I created a Service Account to enable the server to have access.
How do I load the service account credentials without pushing they key itself to GitHub?
I tried adding the key as described in this issue, but that didn't work.
AFAIK setting an environment variable in Vercel is not helpful because Google environment variables require a path/JSON file (vs. simply text).
Rather than using a path to a JSON file, you can create an object and include environment variables as the values for each object key. For example:
admin.initializeApp({
credential: admin.credential.cert({
client_email: process.env.FIREBASE_CLIENT_EMAIL,
private_key: process.env.FIREBASE_PRIVATE_KEY,
project_id: 'my-project'
}),
databaseURL: 'https://my-project.firebaseio.com'
});
Then, you can add the environment variables inside your project settings in Vercel.
Adding to #leerob's answer, I found that putting quotes around the FIREBASE_PRIVATE_KEY environment variable in my .env file fixed an error I kept getting relating to the PEM file when making a request. I didn't need any quotes around the key for calls to the standard firebase library though.
This was the config I used to access the Google Cloud Storage API from my app:
const { Storage } = require('#google-cloud/storage');
const storage = new Storage({ projectId: process.env.FIREBASE_PROJECT_ID,
credentials: { client_email: process.env.FIREBASE_CLIENT_EMAIL,
private_key: process.env.FIREBASE_PRIVATE_KEY_WITH_QUOTES
}
})
I had this problem too but with google-auth-library. Most of Googles libraries provide a way to add credentials through a options object that you pass when initializing it. To be able to get information from Google Sheets or Google Forms you can for example do this:
const auth = new GoogleAuth({
credentials:{
client_id: process.env.GOOGLE_CLIENT_ID,
client_email: process.env.GOOGLE_CLIENT_EMAIL,
project_id: process.env.GOOGLE_PROJECT_ID,
private_key: process.env.GOOGLE_PRIVATE_KEY
},
scopes: [
'https://www.googleapis.com/auth/someScopeHere',
'https://www.googleapis.com/auth/someOtherScopeHere'
]
});
You can just copy the info from your credentials.json file to the corresponding environment variables. Just take care that when your working on localhost you will need to have the private_key in double quotes but when you put it into Vercel you should not include the quotes.

Call azure function secured by AAD using client id and client secret

I have an Azure Function secured by the Azure Active Directory.
I can successfully call the application from the browser provided that I'm logged.
Now, what I want to do is to call that function from an application that doesn't use the username and password in order to authenticate but rather a trusted application client id and client secret.
I tried to use for that purpose the same application (say ApplicationA) that has been created for securing the function application.
I went to the App Registrations, found the application created by configuring the Azure Active Directory authentication provider in the Function App Authentication/Authorization settings (ApplicationA) and I set a Client Secret for it.
Now I'm able to obtain a token using said application Client Id and the generated secret.
But when I'm trying to call the Function using that token I'm getting 401 error.
I tried the same operation with a newly created application in App Registration (say ApplicationB) but with the same result, I'm still getting 401.
I even tried to add a Reader Role for that application in Subscriptions but then again - 401 when calling Function with the token.
I know, there's something like API permissions in the application settings.
So I tried to add the permission for ApplicationB to ApplicationA user_impersonation but it also doesn't work so that's not the way.
So how do I grant the permission for a registered Application for which I'm able to acquire a token so I can use this token to call the Function?
EDIT:
I'm using the following code for token acquisition:
using Microsoft.Identity.Client;
string ClientId = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
string ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string TenantId = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
string Authority = $"https://login.microsoftonline.com/{TenantId}";
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(ClientId)
.WithClientSecret(ClientSecret)
.WithAuthority(new Uri(Authority))
.Build();
AuthenticationResult result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
string token = result.AccessToken;
You need to register at least one application permission (appRole with allowed member type Application) via the manifest into the functions app registration.
Then you can assign the application permission to the client app.
You can see my article for more info on defining permissions: https://joonasw.net/view/defining-permissions-and-roles-in-aad
Example app permission in manifest:
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"description": "Allow the application to read all things as itself.",
"displayName": "Read all things",
"id": "32028ccd-3212-4f39-3212-beabd6787d81",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Things.Read.All"
}
],
You need to define the id yourself, it just needs to be a GUID.
The value is what is sent in the tokens in the roles claim.
Description and display name are just for how the permissions shows up in the UI.

How to use Google Drive for .net application?

I've got a .net core application running on the server and I would like to store my app data directly on Google Drive. I use regular Google account for it with Google Drive API enabled (it seems to be enabled) and service account with credentials stored in json file. Here's code to create Google Drive service in my application:
private DriveService CreateDriveServiceClient() => new DriveService(
new BaseClientService.Initializer()
{
HttpClientInitializer = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(this.config.ClientEmail)
{
Scopes = new string[]
{
DriveService.Scope.DriveAppdata,
},
}.FromPrivateKey(this.config.PrivateKey))
});
It uses scope https://www.googleapis.com/auth/drive.appdata, which leads to string drive.appdata
However I get the following error:
Insufficient Permission: Request had insufficient authentication scopes.] Location[ - ] Reason[insufficientPermissions] Domain[global]
when trying to acces at least client.Drives.List().Execute();
It seems to me that I need to add a role to access drive.appdata scope. But I can't find an appropriate role in Google console, neither I can't find this scope when creating a new role for service account
How can I add permissions to service account to use Google Drive?
According to the Google Drive API v3 documentation:
This request requires authorization with at least one of the following scopes:
https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/drive.readonly
Therefore, if you want to be able to use the Drive API properly, you should change the scope to one of those provided above.
You can also test the API on the Google Drive API v3 here.
Furthermore, I suggest you check these links for more information:
Google Drive API v3;
OAuth2.0 Playground.

Azure AD Redirect URL Using Application Gateway

We have an ASP Core 2.0 App working nicely with Azure AD on the private network. However, we've been playing around with the Azure Application Gateway, investigating the possibility of allowing access to the app from outside for remote workers etc.
We have registered the app on the Gateway, and, once logged in with Azure AD, the anonymous front page is accessible via ourapp.msappproxy.net. However, when signing in (again?) in the app, the client is redirected back to intervalServer/signin-oidc which fails as it is not accessible externally.
While I doubt this is any part of the solution, I have tried overriding the redirect "CallbackPath": "/signin-oidc", to absolute path ourapp.msappproxy.net/signin-oidc but I can't seem to work out how. Changing the reply URL in Azure Portal doesn't help either (although I doubted it would, this is just for verification right?).
I can't seem to find any guidance on this on this particular scenario, so that would be welcome. Otherwise, I'm left pondering the following:
1, If I could change the redirect to ourapp.msappproxy.net/signin-oidc, would that solve the sign in issue?
2, Do I even need an additional sign in step, or should I be changing the app to accept AzureAppProxyUserSessionCookie or AzureAppProxyAccessCookie? (If that's even an option?)
Thanks to rfcdejong in the comments for putting me on track. In our case I was able use Azure AD with the Azure Application Gateway by overriding OnRedirectToIdentityProvider event and supplying the proxy url in ConfigureServices
services.AddAuthentication(...)
.AddOpenIdConnect(options =>
{
options.ClientId = Configuration["Authentication:AzureAD:ClientId"];
options.Authority = Configuration["Authentication:AzureAd:Authority"];
options.CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"];
if (IsProduction) // So that I can use the original redirect to localhost in development
{
Task RedirectToIdentityProvider(RedirectContext ctx)
{
ctx.ProtocolMessage.RedirectUri = "https://ourapp.msappproxy.net/signin-oidc";
return Task.FromResult(0);
}
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = RedirectToIdentityProvider
};
}
})
The return URI needs to be configured to match for the app in Azure Portal.
Users also need to be assigned, but the internal app is now available anywhere without requiring direct access to the server.