-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from permutive-engineering/feature/initial-modules
Initial modules
- Loading branch information
Showing
30 changed files
with
1,735 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,226 @@ | ||
@DESCRIPTION@ | ||
|
||
```scala mdoc:toc | ||
``` | ||
|
||
## Installation | ||
|
||
Add the following line to your build.sbt file: | ||
Add the following line to your `build.sbt` file: | ||
|
||
```sbt | ||
libraryDependencies += "@ORGANIZATION@" %% "@NAME@" % "@VERSION@" | ||
``` | ||
|
||
## Usage | ||
|
||
This library provides a class `TokenProvider` that is able to retrieve a | ||
specific type of access token from [Google OAuth 2.0] API. | ||
|
||
### Available token providers | ||
|
||
```scala mdoc:invisible | ||
import java.security.KeyPairGenerator | ||
import java.security.interfaces.RSAPrivateKey | ||
|
||
import scala.concurrent.duration._ | ||
|
||
import cats.effect.IO | ||
import cats.effect.Resource | ||
|
||
import fs2.io.file.Path | ||
import org.http4s.client.Client | ||
import org.http4s.syntax.all._ | ||
import org.http4s.Response | ||
import retry.RetryPolicies._ | ||
|
||
val httpClient: Client[IO] = Client[IO] { _ => Resource.pure(Response[IO]())} | ||
val pathToServiceAccountFile = Path("") | ||
val pathToClientSecretsPath = Path("") | ||
val pathToRefreshTokenPath = Path("") | ||
val privateKey = KeyPairGenerator.getInstance("RSA").generateKeyPair().getPrivate().asInstanceOf[RSAPrivateKey] | ||
``` | ||
|
||
#### Identity | ||
|
||
Retrieves an [Identity Token] using Google's metadata server for a specific audience. | ||
|
||
Identity tokens can be used for calling Cloud Run services. | ||
|
||
**Important!** This method can only be run from within a workload container in | ||
GCP. The call will fail otherwise. | ||
|
||
```scala mdoc:silent | ||
import com.permutive.gcp.auth.TokenProvider | ||
|
||
val audience = uri"https://my-run-app.a.run.app" | ||
|
||
TokenProvider.identity[IO](httpClient, audience) | ||
``` | ||
|
||
#### Service-Account | ||
|
||
Retrieves a [Google Service Account Token] either via the | ||
[instance metadata API] (if running from a GCP workload) or using a | ||
specific service account file. | ||
|
||
```scala mdoc:silent | ||
import com.permutive.gcp.auth.TokenProvider | ||
import com.permutive.gcp.auth.models.ClientEmail | ||
|
||
// Retrieves a workload service account token using | ||
// Google's metadata server. | ||
TokenProvider.serviceAccount[IO](httpClient) | ||
|
||
// Retrieves a service account token using a specific | ||
// file and scopes | ||
TokenProvider.serviceAccount[IO]( | ||
pathToServiceAccountFile, | ||
scope = "https://www.googleapis.com/auth/bigquery" :: Nil, | ||
httpClient | ||
) | ||
|
||
// Retrieves a service account token using a specific | ||
// email/key/scopes | ||
TokenProvider.serviceAccount[IO]( | ||
ClientEmail("[email protected]"), | ||
privateKey: RSAPrivateKey, | ||
scope = "https://www.googleapis.com/auth/bigquery" :: Nil, | ||
httpClient | ||
) | ||
``` | ||
|
||
#### User-Account | ||
|
||
Retrieves a [Google User Account Token] either using the application default | ||
credentials or from a specific path. | ||
|
||
```scala mdoc:silent | ||
import com.permutive.gcp.auth.TokenProvider | ||
import com.permutive.gcp.auth.models.ClientId | ||
import com.permutive.gcp.auth.models.ClientSecret | ||
import com.permutive.gcp.auth.models.RefreshToken | ||
|
||
// Retrieves a user account token using a specific file | ||
// for the secrets and token | ||
TokenProvider.userAccount[IO]( | ||
pathToClientSecretsPath, | ||
pathToRefreshTokenPath, | ||
httpClient | ||
) | ||
|
||
// Retrieves a service account token using a specific | ||
// client-id/client-secret/refresh-token | ||
TokenProvider.userAccount[IO]( | ||
ClientId("client-id"), | ||
ClientSecret("client-secret"), | ||
RefreshToken("refresh-token"), | ||
httpClient | ||
) | ||
|
||
// Retrieves a user account token using the application | ||
// default credentials | ||
TokenProvider.userAccount[IO](httpClient) | ||
``` | ||
|
||
### Creating and auto-refreshing & cached `TokenProvider` | ||
|
||
You can use `TokenProvider.cached` to create an auto-refreshing & cached | ||
version of any `TokenProvider` that will cache each token generated for | ||
the lifespan of that token and then generates a new one. | ||
|
||
```scala mdoc:silent | ||
import com.permutive.gcp.auth.TokenProvider | ||
|
||
val tokenProvider = | ||
TokenProvider.userAccount[IO](httpClient) | ||
|
||
TokenProvider.cached[IO] | ||
.safetyPeriod(4.seconds) // 1. | ||
.onRefreshFailure { case (_, _) => IO.unit } | ||
.onExhaustedRetries(_ => IO.unit) | ||
.onNewToken { case (_, _) => IO.unit } | ||
.retryPolicy(constantDelay[IO](200.millis)) // 2. | ||
.build(tokenProvider) | ||
|
||
/** | ||
* 1. How much time less than the indicated expiry to | ||
* cache a token for | ||
* 2. Defaults to 5 retries with a delay between each | ||
* of 200 milliseconds. | ||
*/ | ||
``` | ||
|
||
### Creating an auto-authenticated http4s `Client` | ||
|
||
Once you have a `TokenProvider` created, you can use its `clientMiddleware` | ||
method to wrap an http4s' `Client` ensuring every request coming out from it | ||
will contain an `Authorization` header with the access token provided by the | ||
`TokenProvider`. | ||
|
||
```scala mdoc:silent | ||
import com.permutive.gcp.auth.TokenProvider | ||
|
||
TokenProvider | ||
.userAccount[IO](httpClient) | ||
.map(_.clientMiddleware(httpClient)) | ||
``` | ||
|
||
### Loading a different `TokenProvider` depending on the environment with [pureconfig] | ||
|
||
The library also provides a [pureconfig] integration that simplifies the process | ||
of using a different `TokenProvider` on different environments. For example, you | ||
may want to use the workload service-account when running from GCP, but would | ||
want to use a user-account when running your service locally, or use a no-op | ||
access token when running in tests. You can simplify that process by loading | ||
the appropriate `TokenProvider` using pureconfig: | ||
|
||
1. Add the following line to your `build.sbt` file: | ||
|
||
```sbt | ||
libraryDependencies += "@ORGANIZATION@" %% "@NAME@-pureconfig" % "@VERSION@" | ||
``` | ||
|
||
2. Use the following type in your configuration class: | ||
|
||
```scala mdoc:reset:silent | ||
import com.permutive.gcp.auth.pureconfig._ | ||
|
||
case class Config(tokenType: TokenType) | ||
``` | ||
|
||
3. In your `application.conf` file provide the appropriate type: | ||
|
||
```conf | ||
token-type = "user-account" | ||
token-type = "service-account" | ||
token-type = "no-op" | ||
``` | ||
|
||
4. When you want to instantiate your `TokenProvider` simply use: | ||
|
||
```scala mdoc:invisible | ||
import cats.effect.IO | ||
import cats.effect.Resource | ||
|
||
import org.http4s.client.Client | ||
import org.http4s.Response | ||
|
||
val httpClient: Client[IO] = Client[IO] { _ => Resource.pure(Response[IO]())} | ||
val config = Config(TokenType.UserAccount) | ||
``` | ||
|
||
```scala mdoc:silent | ||
val tokenProvider = config.tokenType.tokenProvider(httpClient) | ||
``` | ||
|
||
## Contributors to this project | ||
|
||
@CONTRIBUTORS_TABLE@ | ||
|
||
[Google OAuth 2.0]: https://developers.google.com/identity/protocols/OAuth2 | ||
[`TokenProvider`]: modules/google-auth/src/main/scala/com/permutive/google/auth/TokenProvider.scala | ||
[Google Service Account Token]: https://developers.google.com/identity/protocols/OAuth2ServiceAccount | ||
[Google User Account Token]: https://developers.google.com/identity/protocols/OAuth2WebServer | ||
[Identity Token]: https://cloud.google.com/run/docs/securing/service-identity#fetching_identity_and_access_tokens_using_the_metadata_server | ||
[instance metadata API]: https://cloud.google.com/compute/docs/access/authenticate-workloads | ||
[pureconfig]: https://pureconfig.github.io |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
modules/gcp-auth-pureconfig/src/main/scala/com/permutive/gcp/auth/pureconfig/TokenType.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* Copyright 2024 Permutive Engineering <https://permutive.com> | ||
* | ||
* 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. | ||
*/ | ||
|
||
package com.permutive.gcp.auth.pureconfig | ||
|
||
import cats.effect.Concurrent | ||
import cats.syntax.all._ | ||
|
||
import _root_.pureconfig.ConfigReader | ||
import com.permutive.gcp.auth.TokenProvider | ||
import com.permutive.gcp.auth.models.AccessToken | ||
import fs2.io.file.Files | ||
import org.http4s.client.Client | ||
|
||
/** Provides a convenient way to initialise a [[com.permutive.gcp.auth.TokenProvider TokenProvider]] using pureconfig. | ||
* | ||
* Allowed values when reading from configuration files are `user-account`, `service-account` and `no-op`. Check | ||
* [[TokenType.tokenProvider]] for more information on how a [[com.permutive.gcp.auth.TokenProvider TokenProvider]] is | ||
* created from these values. | ||
*/ | ||
sealed trait TokenType { | ||
|
||
/** Creates a [[com.permutive.gcp.auth.TokenProvider TokenProvider]] using a different method depending on the | ||
* instance: | ||
* | ||
* - [[TokenType.UserAccount]]: the provider will be created using `TokenProvider.userAccount`. | ||
* - [[TokenType.ServiceAccount]]: the provider will be created using `TokenProvider.serviceAccount`. | ||
* - [[TokenType.NoOp]]: will return a provider that always returns | ||
* [[com.permutive.gcp.auth.models.AccessToken.noop AccessToken.noop]]. | ||
*/ | ||
def tokenProvider[F[_]: Files: Concurrent](httpClient: Client[F]): F[TokenProvider[F]] = this match { | ||
case TokenType.UserAccount => TokenProvider.userAccount[F](httpClient) | ||
case TokenType.ServiceAccount => TokenProvider.serviceAccount[F](httpClient).pure[F] | ||
case TokenType.NoOp => TokenProvider.const(AccessToken.noop).pure[F] | ||
} | ||
|
||
} | ||
|
||
object TokenType { | ||
|
||
case object UserAccount extends TokenType | ||
|
||
case object ServiceAccount extends TokenType | ||
|
||
case object NoOp extends TokenType | ||
|
||
implicit val TokenTypeConfigReader: ConfigReader[TokenType] = ConfigReader.fromStringOpt { | ||
case "user-account" => TokenType.UserAccount.some | ||
case "service-account" => TokenType.ServiceAccount.some | ||
case "no-op" => TokenType.NoOp.some | ||
} | ||
|
||
} |
5 changes: 5 additions & 0 deletions
5
...cp-auth-pureconfig/src/test/resources/.config/gcloud/application_default_credentials.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"client_id":"my-client-id", | ||
"client_secret":"my-client-secret", | ||
"refresh_token":"refresh_token" | ||
} |
Oops, something went wrong.