About IG
IG as a proxy
Many organizations have existing services that cannot easily be integrated into newer architectures. Similarly, many existing client applications cannot communicate with services. This section describes how IG acts as an intermediary, or proxy, between clients and services.
IG as a reverse proxy
IG as a reverse proxy server is an intermediate connection point between external clients and internal services. IG intercepts client requests and server responses, enforcing policies, and routing and shaping traffic. The following image illustrates IG as a reverse proxy:
IG provides the following features as a reverse proxy:
-
Access management integration
-
Application and API security
-
Credential replay
-
OAuth 2.0 support
-
OpenID Connect 1.0 support
-
Network traffic control
-
Proxy with request and response capture
-
Request and response rewriting
-
SAML 2.0 federation support
-
Single sign-on (SSO)
IG as a forward proxy
In contrast, IG as a forward proxy is an intermediate connection point between an internal client and an external service. IG regulates outbound traffic to the service, and can adapt and enrich requests. The following image illustrates IG as a forward proxy:
IG provides the following features as a forward proxy:
-
Addition of authentication or authorization to the request
-
Addition of tracer IDs to the requests
-
Addition or removal of request headers or scopes
IG as a microgateway
IG is optimized to run as a microgateway in containerized environments. Use IG with business microservices to separate the security concerns of your applications from their business logic. For example, use IG with the ForgeRock Token Validation Microservice to provide access token validation at the edge of your namespace.
For an example, refer to IG as a microgateway. The following image illustrates the request flow in an example deployment:
The request is processed in the following sequence:
-
A client requests access to Secured Microservice A, providing a stateful OAuth 2.0 access token as credentials.
-
Microgateway A intercepts the request, and passes the access token for validation to the Token Validation Microservice, using the
/introspect
endpoint. -
The Token Validation Microservice requests the Authorization Server to validate the token.
-
The Authorization Server introspects the token, and sends the introspection result to the Token Validation Microservice.
-
The Token Validation Microservice caches the introspection result, and sends it to Microgateway A, which forwards the result to Secured Microservice A.
-
Secured Microservice A uses the introspection result to decide how to process the request. In this case, it continues processing the request. Secured Microservice A asks for additional information from Secured Microservice B, providing the validated token as credentials.
-
Microgateway B intercepts the request, and passes the access token to the Token Validation Microservice for validation, using the
/introspect
endpoint. -
The Token Validation Microservice retrieves the introspection result from the cache, and sends it back to Microgateway B, which forwards the result to Secured Microservice B.
-
Secured Microservice B uses the introspection result to decide how to process the request. In this case it passes its response to Secured Microservice A, through Microgateway B.
-
Secured Microservice A passes its response to the client, through Microgateway A.
Processing requests and responses
The following sections describe how IG processes requests and responses:
IG object model
IG processes HTTP requests and responses by passing them through user-defined chains of filters and handlers. The filters and handlers provide access to the request and response at each step in the chain, and make it possible to alter the request or response, and collect contextual information.
The following image illustrates a typical sequence of events when IG processes a request and response through a chain:
When IG processes a request, it first builds an object representation of the request, including parsed query/form parameters, cookies, headers, and the entity. IG initializes a runtime context to provide additional metadata about the request and applied transformations. IG then passes the request representation into the chain.
In the request flow, filters modify the request representation and can enrich the runtime context with computed information. In the ClientHandler, the entity content is serialized, and additional query parameters can be encoded as described in RFC 3986: Query.
In the response flow, filters build a response representation with headers and the entity.
The route configuration in Adding headers and logging results demonstrates the flow through a chain to a protected application.
Configuring IG
The way that IG processes requests and responses is defined by the
configuration files admin.json
and config.json
, and by Route
configuration files. For information about the different files used by
IG, refer to
Configuration directories and files.
Configuration files are flat JSON files that define objects with the following parts:
-
name
: A unique string to identify the object. When you declare inline objects, the name is not required. -
type
: The type name of the object. IG defines many object types for different purposes. -
config
: Additional configuration settings for the object. The content of the configuration object depends on its type. For information about each object type available in the IG configuration, see the Reference.If all of the configuration settings for the type are optional, the
config
field is also optional. The object uses default settings when theconfig
field isn’t configured, or is configured as an empty object ("config": {}
), or is configured as null ("config": null
).Filters, handlers, and other objects whose configuration settings are defined by strings, integers, or booleans, can be defined by expressions that match the expected type.
For information about the objects available, see admin.json, config.json and Route. An IG route typically contains at least the following parts:
-
handler
: An object to specify the point where the request enters the route. If the handler type is a Chain, the request is dispatched to a list of filters, and then to another handler.Handler objects produce a response for a request, or delegate the request to another handler. Filter objects transform data in the request, response, or context, or perform an action when the request or response passes through the filter.
-
baseURI
: A handler decorator to override the scheme, host, and port of the request URI. After a route processes a request, it reroutes the request to thebaseURI
, which most usually points to the application or service that IG is protecting. -
heap
: A collection of named objects configured in the top level ofconfig.json
or in individual routes. Heap objects can be configured once and used multiple times in the configuration.A heap object in a route can be used in that route. A heap object in
config.json
can be used across the whole configuration, unless it is overridden in a route. -
condition
: An object to define a condition that a request must meet. A route can handle a request ifcondition
is not defined, or if the condition resolves totrue
.
Routes inherit settings from their parent configurations. This means that you
can configure objects in the config.json
heap, for example, and then
reference those objects by name in any other IG configuration.
Configuration directories and files
By default, IG configuration files are located under $HOME/.openig
(on Windows appdata\OpenIG
). For information about how to change
the location, refer to
Change the base location of the IG configuration.
Using comments in IG configuration files
The JSON format does not specify a notation for comments. If IG does not recognize a JSON field name, it ignores the field. As a result, it is possible to use comments in configuration files.
The following conventions are available for commenting:
-
A
comment
field to add text comments. The following example includes a text comment.{ "name": "capture", "type": "CaptureDecorator", "comment": "Write request and response information to the logs", "config": { "captureEntity": true } }
-
An underscore (
_
) to comment a field temporarily. The following example comments out"captureEntity": true
, and as a result it uses the default setting ("captureEntity": false
).{ "name": "capture", "type": "CaptureDecorator", "config": { "_captureEntity": true } }
Development mode and production mode
IG operates in the following modes:
-
Development mode (mutable mode)
In development mode, by default all endpoints are open and accessible.
You can create, edit, and deploy routes through IG Studio, and manage routes through Common REST, without authentication or authorization.
Use development mode to evaluate or demo IG, or to develop configurations on a single instance. This mode is not suitable for production.
For information about how to switch to development mode, refer to Switching from production mode to development mode.
For information about restricting access to Studio in development mode, refer to Restricting access to Studio.
-
Production mode (immutable mode)
In production mode, the
/routes
endpoint is not exposed or accessible.Studio is effectively disabled, and you cannot manage, list, or even read routes through Common REST.
By default, other endpoints, such as
/share
andapi/info
are exposed to the loopback address only. To change the default protection for specific endpoints, configure an ApiProtectionFilter inadmin.json
and add it to the IG configuration.For information about how to switch to production mode, refer to Switching from development mode to production mode.
After installation, IG is by default in production mode.
Decorators
Decorators are heap objects to extend what another object can do. IG
defines baseURI
, capture
, and timer
decorators that you can use
without explicitly configuring them. For more information about the types of
decorators provided by IG, refer to
Decorators.
Decorating objects, the route handler, and the heap
Use decorations that are compatible with the object type. For example, timer
records the time to process filters and handlers, but does not record
information for other object types. Similarly, baseURI
overrides the scheme,
host, and ports, but has no other effect.
In a route, you can decorate individual objects, the route handler, and the heap. IG applies decorations in this order:
-
Decorations declared on individual objects. Local decorations that are part of an object’s declaration are inherited wherever the object is used.
-
globalDecorations declared in parent routes, then in child routes, and then in the current route.
-
Decorations declared on the route handler.
Decorating individual objects in a route
To decorate individual objects, add the decorator’s name value as a top-level
field of the object, next to type
and config
.
In this example, the decorator captures all requests going into the SingleSignOnFilter, and all responses coming out of the SingleSignOnFilter:
{
"heap": [
{
"name": "SystemAndEnvSecretStore-1",
"type": "SystemAndEnvSecretStore"
},
{
"name": "AmService-1",
"type": "AmService",
"config": {
"agent": {
"username": "ig_agent",
"passwordSecretId": "agent.secret.id"
},
"secretsProvider": "SystemAndEnvSecretStore-1",
"url": "http://am.example.com:8088/openam/"
}
}
],
"handler": {
"type": "Chain",
"config": {
"filters": [
{
"capture": "all",
"type": "SingleSignOnFilter",
"config": {
"amService": "AmService-1"
}
}
],
"handler": "ReverseProxyHandler"
}
}
}
Decorating the route handler
To decorate the handler for a route, add the decorator as a top-level field of the route.
In this example, the decorator captures all requests and responses that traverse the route:
{
"heap": [
{
"name": "SystemAndEnvSecretStore-1",
"type": "SystemAndEnvSecretStore"
},
{
"name": "AmService-1",
"type": "AmService",
"config": {
"agent" : {
"username" : "ig_agent",
"passwordSecretId" : "agent.secret.id"
},
"secretsProvider": "SystemAndEnvSecretStore-1",
"url": "http://am.example.com:8088/openam/"
}
}
],
"capture": "all",
"handler": {
"type": "Chain",
"config": {
"filters": [
{
"type": "SingleSignOnFilter",
"config": {
"amService": "AmService-1"
}
}
],
"handler": "ReverseProxyHandler"
}
}
}
Decorating the route heap
To decorate all compatible objects in a route, configure globalDecorators as a top-level field of the route. The globalDecorators field takes a map of the decorations to apply.
To decorate all compatible objects declared in config.json
or
admin.json
, configure globalDecorators as a top-level field in
config.json
or admin.json
.
In the following example, the route has capture and timer decorations. The capture decoration applies to AmService, Chain, SingleSignOnFilter, and ReverseProxyHandler. The timer decoration doesn’t apply to AmService because it is not a filter or handler, but does apply to Chain, SingleSignOnFilter, and ReverseProxyHandler:
{
"globalDecorators":
{
"capture": "all",
"timer": true
},
"heap": [
{
"name": "SystemAndEnvSecretStore-1",
"type": "SystemAndEnvSecretStore"
},
{
"name": "AmService-1",
"type": "AmService",
"config": {
"agent": {
"username": "ig_agent",
"passwordSecretId": "agent.secret.id"
},
"secretsProvider": "SystemAndEnvSecretStore-1",
"url": "http://am.example.com:8088/openam/"
}
}
],
"handler": {
"type": "Chain",
"config": {
"filters": [
{
"type": "SingleSignOnFilter",
"config": {
"amService": "AmService-1"
}
}
],
"handler": "ReverseProxyHandler"
}
}
}
Decorating a named object differently in different parts of the configuration
When a filter or handler is configured in config.json
or in the heap,
it can be used many times in the configuration. To decorate each use of the
filter or handler individually, use a Delegate. For more information, refer to
Delegate
In the following example, an AmService heap object configures an amHandler
to
delegate tasks to ForgeRockClientHandler
, and capture all requests and
responses passing through the handler.
{
"type": "AmService",
"config": {
"agent" : {
"username" : "ig_agent",
"passwordSecretId" : "agent.secret.id"
},
"secretsProvider": "SystemAndEnvSecretStore-1",
"amHandler": {
"type": "Delegate",
"capture": "all",
"config": {
"delegate": "ForgeRockClientHandler"
}
},
"url": "http://am.example.com:8088/openam"
}
}
You can use the same ForgeRockClientHandler
in another part of the
configuration, in a different route for example, without adding a capture
decorator. Requests and responses that pass through that use of the handler are
not captured.
Decorating IG’s interactions with AM
To log interactions between IG and AM, delegate message handling to a ForgeRockClientHandler, and capture the requests and responses passing through the handler. When the ForgeRockClientHandler communicates with an application, it sends ForgeRock Common Audit transaction IDs.
In the following example, the accessTokenResolver
delegates message handling
to a decorated ForgeRockClientHandler:
"accessTokenResolver": {
"name": "token-resolver-1",
"type": "TokenIntrospectionAccessTokenResolver",
"config": {
"amService": "AmService-1",
"providerHandler": {
"capture": "all",
"type": "Delegate",
"config": {
"delegate": "ForgeRockClientHandler"
}
}
}
}
To try the example, replace the accessTokenResolver
in the IG route of
Validate access tokens through the introspection endpoint. Test the setup as described for the
example, and note that the route’s log file contains an HTTP call to the
introspection endpoint.
Using multiple decorators for the same object
Decorations can apply more than once. For example, if you set a decoration on a route and another decoration on an object defined within the route, IG applies the decoration twice. In the following route, the request is captured twice:
{
"handler": {
"type": "ReverseProxyHandler",
"capture": "request"
},
"capture": "all"
}
When an object has multiple decorations, the decorations are applied in the order they appear in the JSON.
In the following route, the handler is decorated with a baseURI
first, and a
capture
second:
{
"name": "myroute",
"baseURI": "http://app.example.com:8081",
"capture": "all",
"handler": {
"type": "StaticResponseHandler",
"config": {
"status": 200,
"headers": {
"Content-Type": [ "text/plain; charset=UTF-8" ]
},
"entity": "Hello world, from myroute!"
}
},
"condition": "${find(request.uri.path, '^/myroute1')}"
}
The decoration can be represented as capture[ baseUri[ handler ] ]
. When a
request is processed, it is captured, and then rebased, and then processed by
the handler: The log for this route shows that the capture occurs before the
rebase:
2018-09-10T13:23:18,990Z | INFO | http-nio-8080-exec-1 | o.f.o.d.c.C.c.top-level-handler | @myroute |
--- (request) id:f792d2ad-4409-4907-bc46-28e1c3c19ac3-7 --->
GET http://ig.example.com:8080/myroute HTTP/1.1
...
Conversely, in the following route, the handler is decorated with a capture
first, and a baseURI
second:
{
"name": "myroute",
"capture": "all",
"baseURI": "http://app.example.com:8081",
"handler": {
"type": "StaticResponseHandler",
"config": {
"status": 200,
"headers": {
"Content-Type": [ "text/plain; charset=UTF-8" ]
}
"entity": "Hello, world from myroute1!"
}
},
"condition": "${find(request.uri.path, '^/myroute')}"
}
The decoration can be represented as baseUri[ capture[ handler ] ]
. When a
request is processed, it is rebased, and then captured, and then processed by
the handler. The log for this route shows that the rebase occurs before the
capture:
2018-09-10T13:07:07,524Z | INFO | http-nio-8080-exec-1 | o.f.o.d.c.C.c.top-level-handler | @myroute |
--- (request) id:3c26ab12-3cc0-403e-bec6-43bf5621f657-7 --->
GET http://app.example.com:8081/myroute HTTP/1.1
...
Guidelines for naming decorators
To prevent unwanted behavior, consider the following points when you name decorators:
-
Avoid decorators named
comment
orcomments
, and avoid reserved field names. Instead of using alphanumeric field names, consider using dots in your decorator names, such asmy.decorator
. -
For heap objects, avoid the reserved names
config
,name
, andtype
. -
For routes, avoid the reserved names
auditService
,baseURI
,condition
,globalDecorators
,heap
,handler
,name
,secrets
, andsession
. -
In
config.json
, avoid the reserved nametemporaryStorage
.
Configuration parameters declared as property variables
Configuration parameters, such as host names, port numbers, and directories, can be declared as property variables in the IG configuration or in an external JSON file. The variables can then be used in expressions in routes and in config.json
to set the value of configuration parameters.
Properties can be inherited across the router, so a property defined in config.json
can be used in any of the routes in the configuration.
Storing the configuration centrally and using variables for parameters that can be different for each installation makes it easier to deploy IG in different environments without changing a single line in your route configuration.
For more information, see Route properties.
Changing the configuration and restarting IG
You can change routes or change a property that is read at runtime or that relies on a runtime expression without needing to restart IG to take the change into account.
Stop and restart IG only when you make the following changes:
-
Change the configuration of any route, when the
scanInterval
of Router isdisabled
(see Router). -
Add or change an external object used by the route, such as an environment variable, system property, external URL, or keystore.
-
Add or update
config.json
oradmin.json
.
Understanding IG APIs with API descriptors
Common REST endpoints in IG serve API descriptors at runtime. When you retrieve an API descriptor for an endpoint, a JSON that describes the API for that endpoint is returned.
To help you discover and understand APIs, you can use the API descriptor with a tool such as Swagger UI to generate a web page that helps you to view and test the different endpoints.
When you start IG, or add or edit routes, registered endpoint locations
for the routes hosted by the main router are written in
$HOME/.openig/logs/route-system.log
, where $HOME/.openig
is the
instance directory. Endpoint locations for subroutes are written to other log
files. To retrieve the API descriptor for a specific endpoint, append one of
the following query string parameters to the endpoint:
-
_api
, to represent the API accessible over HTTP. This OpenAPI descriptor can be used with endpoints that are complete or partial URLs.The returned JSON respects the OpenAPI specification and can be consumed by Swagger tools, such as Swagger UI.
-
_crestapi
, to provide a compact representation that is independent of the transport protocol. This ForgeRock® Common REST (Common REST) API descriptor cannot be used with partial URLs.The returned JSON respects a ForgeRock proprietary specification dedicated to describe Common REST endpoints.
For more information about Common REST API descriptors, see Common REST API Documentation.
With IG running as described in the Getting started, run the following query to generate a JSON that describes the router operations supported by the endpoint:
$ curl http://ig.example.com:8080/openig/api/system/objects/_router/routes?_api
{
"swagger": "2.0",
"info": {
"version": "IG version",
"title": "IG"
},
"host": "0:0:0:0:0:0:0:1",
"basePath": "/openig/api/system/objects/_router/routes",
"tags": [{
"name": "Routes Endpoint"
}],
. . .
Alternatively, generate a Common REST API descriptor by using the ?_crestapi
query string.
With the UMA tutorial running as described in UMA support, run the following query to generate a JSON that describes the UMA share API:
$ curl http://ig.example.com:8080/openig/api/system/objects/_router/routes/00-uma/objects/umaservice/share?_api
{
"swagger": "2.0",
"info": {
"version": "IG version",
"title": "IG"
},
"host": "0:0:0:0:0:0:0:1",
"basePath": "/openig/api/system/objects/_router/routes/00-uma/objects/umaservice/share",
"tags": [{
"name": "Manage UMA Share objects"
}],
. . .
Alternatively, generate a Common REST API descriptor by using the
?_crestapi
query string.
Run a query to generate a JSON that describes the API for the main router and its subsequent endpoints. For example:
$ curl http://ig.example.com:8080/openig/api/system/objects/_router?_api
{
"swagger": "2.0",
"info": {
"version": "IG version",
"title": "IG"
},
"host": "ig.example.com:8080",
"basePath": "/openig/api/system/objects/_router",
"tags": [{
"name": "Monitoring endpoint"
}, {
"name": "Manage UMA Share objects"
}, {
"name": "Routes Endpoint"
}],
. . .
Because the above URL is a partial URL, you cannot use the ?_crestapi
query
string to generate a Common REST API descriptor.
Run a query to generate a JSON that describes the APIs provided by the IG instance that is responding to a request. For example:
$ curl http://ig.example.com:8080/openig/api?_api
{
"swagger": "2.0",
"info": {
"version": "IG version",
"title": "IG"
},
"host": "ig.example.com:8080",
"basePath": "/openig/api",
"tags": [{
"name": "Internal Storage for UI Models"
}, {
"name": "Monitoring endpoint"
}, {
"name": "Manage UMA Share objects"
}, {
"name": "Routes Endpoint"
}, {
"name": "Server Info"
}],
. . .
If routes are added after the request is performed, they are not included in the returned JSON.
Because the above URL is a partial URL, you cannot use the ?_crestapi
query
string to generate a Common REST API descriptor.
Sessions
IG uses sessions to group requests from a user agent or other source, and collect information from the requests. When multiple requests are made in the same session, the requests can share the session information. Because session sharing is not thread-safe, it is not suitable for concurrent exchanges.
The following table compares stateful and stateless sessions:
Feature | Stateful sessions | Stateless sessions |
---|---|---|
Cookie size. |
Unlimited. |
Max 4 KBytes. |
Default name of the session cookie. |
|
|
Object types that can be stored in the session. |
Only Java serializable objects, when sessions are replicated. Any object, when sessions are not replicated. |
JSON-compatible types, such as strings, numbers, booleans, null, structures such as arrays, and list and maps containing only JSON-compatible types. |
Session sharing between instances of IG, for load balancing and failover. |
Possible when sessions are replicated on multiple IG instances. Possible when sessions are not replicated, if session stickiness is configured. |
Possible because the session content is a cookie on the user agent, that can be copied to multiple instances of IG. |
Risk of data inconsistency when simultaneous requests modify the content of a session. |
Low because the session content is stored on IG and shared by all exchanges. Processing is not thread-safe. |
Higher because the session content is reconstructed for each request. Concurrent exchanges don’t have the same content. |
Stateful sessions
When a JwtSession is not configured for a request, stateful sessions are created
automatically. Session information is stored in the IG cookie, called
IG_SESSIONID
by default. When the user agent sends a request with the cookie,
the request can access the session information on IG.
When a JwtSession object is configured in the route that processes a request, or
in its ascending configuration (a parent route or config.json
), the
session is always stateless and can’t be stateful.
When a request enters a route without a JwtSession object in the route or its ascending configuration, a stateful session is created lazily.
Session duration is defined by the session
property in
admin.json,
with a default of 30 minutes.
Even if the session is empty, the session remains usable until the timeout.
When IG is not configured for session replication, any object type can be stored in a stateful session.
Because session content is stored on IG, and shared by all exchanges, when IG processes simultaneous requests in a stateful session there is low risk that the data becomes inconsistent. However, sessions are not thread-safe; different requests can simultaneously read and modify a shared session.
Session information is available in SessionContext to downstream handlers and filters. For more information, refer to SessionContext.
Considerations for clustering IG
When a stateful session is replicated on the multiple IG instances, consider the following points:
-
The session can store only object types that can be serialized.
-
The network latency of session replication introduces a delay that can cause the session information of two IG instances to desynchronize.
-
Because the session is replicated on the clustered IG instances, it can be shared between the instances, without configuring session stickiness.
-
When sessions are not shared, configure session stickiness to ensure that load balancers serve requests to the same IG instance. For more information, refer to Prepare for load balancing and failover.
Configuring stateful sessions
To configure stateful sessions, update the session
property of
admin.json.
Stateless sessions
Stateless sessions are provided when a JwtSession object is configured in
config.json
or in a route. For more information about configuring
stateless sessions, refer to
JwtSession.
IG serializes stateless session information as JSON, stores it in a JWT that can be encrypted and then signed, and places the JWT in a cookie. The cookie contains all of the information about the session, including the session attributes as JSON, and a marker for the session timeout.
Only JSON-compatible object types can be stored in stateless sessions. These object types include strings, numbers, booleans, null, structures such as arrays, and list and maps containing only JSON-compatible types.
Stateless sessions are managed as follows:
-
When a request enters a route with a JwtSession object in the route or its ascending configuration, IG creates the SessionContext, verifies the cookie signature, decrypts the content of the cookie, and checks that the current date is before the session timeout.
-
When the request passes through the filters and handlers in the route, the request can read and modify the session content.
-
When the request returns to the the point where the session was created, for example, at the entrance to a route or at
config.json
, IG updates the cookie as follows:-
If the session content has changed, IG serializes the session, creates a new cookie with the new content, encrypts and then signs the new cookie, assigns it an appropriate expiration time, and returns the cookie in the response.
-
If the session is empty, IG deletes the session, creates a new cookie with an expiration time that has already passed, and returns the cookie in the response.
-
If the session content has not changed, IG does nothing.
-
Because the session content is stored in a cookie on the user agent, stateless sessions can be shared easily between IG instances. The cookie is automatically carried over in requests, and any IG instance can unpack and use the session content.
When IG processes simultaneous requests in stateless sessions, there is a high risk that the data becomes inconsistent. This is because the session content is reconstructed for each exchange, and concurrent exchanges don’t have the same content.
IG does not share sessions across requests. Instead, each request has its own session objects that it modifies as necessary, writing its own session to the session cookie regardless of what other requests do.
Session information is available in SessionContext to downstream handlers and filters. For more information, refer to SessionContext.
Secrets
IG uses the ForgeRock Commons Secrets API to manage secrets, such as passwords and cryptographic keys.
Repositories of secrets are managed through secret stores, provided to the
configuration by the SecretsProvider
object or secrets
object. For more
information about these objects and the types of secret stores provided in
IG, refer to
SecretsProvider
and
Secrets object and secret stores.
Secret types
IG uses two secret types:
-
GenericSecret
: An opaque blob of bytes, such as a password or API key, without any metadata. AGenericSecret
cannot be used to perform cryptographic operations. -
CryptoKey
: A secret that contains either a private or shared key, and/or a public certificate. ACryptoKey
contains the secret material itself and its metadata; for example, the associated algorithm or key type. This secret type can be used for cryptographic operations.
For example:
-
A
Base64EncodedSecretStore
can only serve secrets of theGenericSecret
type. -
An
HsmSecretStore
can only server secrets of theCryptoKey
type. -
A
FileSystemSecretStore
can serve secrets of both types.
To learn more about secret store specificities, refer to Secret Stores.
Secret terminology
The following terms are used to describe secrets:
-
Secret ID: A label to indicate the purpose of a secret. A secret ID is generally associated with one or more aliases of a key in a keystore or HSM.
-
Stable ID: A label to identify a secret. The stable ID corresponds to the following values in each type of secret store:
-
Base64EncodedSecretStore: The value of
secret-id
in the"secret-id": "string"
pair. -
FileSystemSecretStore: The filename of a file in the specified directory, without the prefix/suffix defined in the store configuration.
-
HsmSecretStore: The value of an
alias
in asecret-id
/aliases
mapping. -
JwkSetSecretStore: The value of the
kid
of a JWK stored in a JwkSetSecretStore. -
KeyStoreSecretStore: The value of an
alias
in asecret-id
/aliases
mapping. -
SystemAndEnvSecretStore: The name of a system property or environment. variable
-
-
Valid secret: A secret whose purpose matches the secret ID and any purpose constraints. Constraints can include requirements for the following:
-
Secret type, such as signing key or encryption key
-
Cryptographic algorithm, such as Diffie-Hellman and RSA
-
Signature algorithm, such as ES256 and ES384
Constraints are defined when the secret is generated, and cannot be added after.
-
-
Named secret: A valid secret that a secret store can find by using a secret ID and stable ID.
-
Active secret: One of the valid secrets that is considered eligible at the time of use. The way that the active secret is chosen is determined by the type of secret store. For more information, refer to Secrets object and secret stores,
Using keys and certificates
The examples in this doc set use self-signed certificates, but your deployment is likely to use certificates issued by a certificate authority (CA certificates).
The way to obtain CA certificates depends on the certificate authority that you are using, and is not described in this document. As an example, refer to Let’s Encrypt.
Integrate CA certificates by using secret stores:
-
For PEM files, use a FileSystemSecretStore and PemPropertyFormat
-
For PKCS12 keystores, use a KeyStoreSecretStore
For examples, refer to Serve the same certificate for TLS connections to all server names.
Note the following points about using secrets:
-
When IG starts up, it listens for HTTPS connections, using the ServerTlsOptions configuration in
admin.json
. The keys and certificates are fetched at startup. -
Keys and certificates must be present at startup.
-
If keys or certificates change, you must to restart IG.
-
When the
autoRefresh
property of FileSystemSecretStore or KeyStoreSecretStore is enabled, the secret store is automatically reloaded when the filesystem or keystore is changed.
For information about secret stores provided in IG, refer to Secrets object and secret stores.
Validating the signature of signed tokens
IG validates the signature of signed tokens as follows:
-
Named secret resolution:
-
If the JWT contains a
kid
, IG queries the secret stores declared insecretsProvider
orsecrets
to find a named secret, identified by a secret ID and stable ID. -
If a named secret is found, IG then uses the named secret to try to validate the signature. If the named secret can’t validate the signature, the token is considered as invalid.
-
If a named secret isn’t found, IG tries valid secret resolution.
-
-
Valid secret resolution:
-
IG uses the value of
verificationSecretId
as the secret ID, and queries the declared secret stores to find all secrets that match the provided secret ID. -
All matching secrets are returned as valid secrets, in the order that the secret stores are declared, and for KeyStoreSecretStore and HsmSecretStore, in the order defined by the mappings.
-
IG tries to verify the signature with each valid secret, starting with the first valid secret, and stopping when it succeeds.
-
If no valid secrets are returned, or if none of the valid secrets can verify the signature, the token is considered as invalid.
-
For examples where a StatelessAccessTokenResolver uses a secret store to validate the signature of signed tokens, see the example sections of JwkSetSecretStore and KeyStoreSecretStore.
Using multiple secret stores in a configuration
When multiple secrets stores are provided in a configuration, the secrets stores are queried in the following order:
-
Locally in the route, starting with the first secret store in the list, up to the last.
-
In ascending parent routes, starting with the first secret store in each list, up to the last.
-
In
config.json
, starting with the first secret store in the list, up to the last. -
If a secrets store is not configured in
config.json
, the secret is queried in a default SystemAndEnvSecretStore, and a base64-encoded value is expected. -
If a secret is not resolved, an error is produced.
Secrets stores defined in admin.json
can be accessed only by heap
objects in admin.json
.
Algorithms for elliptic curve digital signatures
When the Elliptic Curve Digital Signature Algorithm (ECDSA) is used for signing, and both of the following conditions are met, JWTs are signed with a deterministic ECDSA:
-
Bouncy Castle is installed.
-
The system property
org.forgerock.secrets.preferDeterministicEcdsa
istrue
, which is its default value.
Otherwise, when ECDSA is used for signing, JWTs are signed with a non-deterministic ECDSA.
A non-deterministic ECDSA signature can be verified by the equivalent deterministic algorithm.
For information about deterministic ECDSA, see RFC 6979. For information about Bouncy Castle, see The Legion of the Bouncy Castle.