Class IssuerHttpClient
- All Implemented Interfaces:
AutoCloseable
Why it is built from scratch rather than derived from the ANKASecure client
Deriving it with newBuilder() would be one line and would inherit everything the other
client carries, which is exactly the problem. That client honours
openapi.insecureSkipTlsVerify, a property this SDK's own cli.properties ships as
true for the demo playground: when it is set, the ANKASecure client installs a trust-all
X509TrustManager AND a hostname verifier that returns true for everything. It also
carries a headers logger when debugging is on, and an origin pin aimed at the ANKASecure
deployment. Inheriting that set would mean a consumer's credential is posted to a host whose
certificate was never checked, because of a property about a different server.
So this client shares nothing with it: a fresh builder, no TLS relaxation of any kind, no logger on the fetch path, and its own origin pin.
What it enforces
- The pinned addresses. Its
Dnsanswers with exactly the addresses admission resolved and screened, and throws for any other host. There is no second lookup, so there is no window between the check and the use. - No proxy, unless the operator asked for one in the process environment. A client
that goes through a proxy never consults its own
Dns: it hands the issuer NAME to the proxy, which resolves it, so every address admission screened is bypassed and the whole egress screening is void. That is a JVM-wide setting - onehttps.proxyHostsystem property, or aProxySelectorsomeone installed - so the control would otherwise be switched off from outside this SDK with no signal at all. This client therefore pinsProxy.NO_PROXYand REFUSES, before any socket exists, when a proxy is configured for the issuer without "ANKASECURE_ALLOW_PROXIED_EXTERNAL_ISSUER" being set. SeeproxiedIssuerAllowed(). - The origin pin, twice.
ConfiguredOriginInterceptoris registered as an APPLICATION interceptor and again as a NETWORK interceptor. The network registration alone refuses only afterConnectInterceptorhas run - DNS, TCP and a full TLS handshake with the foreign host have already happened by then, which is a blind connect primitive against any host a response can name. The application registration refuses before the connection exists; the network one still guards anything that rewrites a URL later. - No redirects, in either form. A redirect is the responding server choosing where the next request goes.
- A total deadline. Connect and read timeouts do not bound a TLS handshake that never
finishes;
CALL_TIMEOUTdoes.
It is owned, and therefore closed
One of these is built per resolved credential, not one per process, and it holds a connection
pool of its own. That pool is what makes abandoning one costly: it keeps a live keep-alive TLS
connection to a THIRD-PARTY authorization server, and a long-running application that opened a
session per unit of work would accumulate one per session. So this type is AutoCloseable
and the provider that owns it closes it.
close() releases the dispatcher's executor as well, and the reason for that half is
narrower and worth stating exactly, because the obvious reason is false here. Every call this SDK
makes on this client is SYNCHRONOUS - newCall(...).execute(), with no enqueue
anywhere on the path - and OkHttp creates the dispatcher's executor lazily, so on the SDK's own
path there are no dispatcher threads in existence to leak. okHttpClient() is public,
however, and a caller that enqueues on it would create exactly the non-daemon threads that keep a
JVM alive after the work is done. The pool is why this type must be closed; the dispatcher is
closed because the client it belongs to is reachable.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringThe one value that enables it.static final StringThe one variable that admits an issuer reached through a proxy, and with it the loss of the address screening.static final DurationThe whole-call budget, which is the one that matters here: it covers name resolution, the TLS handshake and every read, so a server that accepts a connection and then stalls the handshake cannot hold this thread indefinitely.static final DurationTCP connect budget.static final DurationPer-read budget once a connection exists. -
Method Summary
Modifier and TypeMethodDescriptionvoidclose()Releases this client's connection pool and dispatcher.static IssuerHttpClientforIssuer(AdmittedIssuer issuer) Builds the issuer client for an admitted issuer, trusting the platform's certificate authorities and nothing else.issuer()okhttp3.OkHttpClientThe underlying client.origin()static booleanWhether this process environment permits reaching an external issuer through a proxy.toString()
-
Field Details
-
CONNECT_TIMEOUT
TCP connect budget. -
READ_TIMEOUT
Per-read budget once a connection exists. -
CALL_TIMEOUT
The whole-call budget, which is the one that matters here: it covers name resolution, the TLS handshake and every read, so a server that accepts a connection and then stalls the handshake cannot hold this thread indefinitely. -
ALLOW_PROXY_ENV_VARIABLE
The one variable that admits an issuer reached through a proxy, and with it the loss of the address screening. Deliberately absent fromAuthSetting, exactly likeExternalIssuerAdmission.ALLOW_PRIVATE_ENV_VARIABLE: a properties file must never be able to widen where this process posts a credential, nor to decide that the screening which chose the destination may be skipped.- See Also:
-
ALLOW_PROXY_ENV_VALUE
The one value that enables it. Anything else - includingtrue- does not.- See Also:
-
-
Method Details
-
forIssuer
Builds the issuer client for an admitted issuer, trusting the platform's certificate authorities and nothing else.- Parameters:
issuer- the admitted issuer; must not benull- Returns:
- the client
- Throws:
ExternalTokenException- if a proxy is configured for this issuer and "ANKASECURE_ALLOW_PROXIED_EXTERNAL_ISSUER" is not set
-
proxiedIssuerAllowed
public static boolean proxiedIssuerAllowed()Whether this process environment permits reaching an external issuer through a proxy.Reads
System.getenv(String)directly, at the point of use, and takes no argument - the same shape, and for the same reason, asExternalIssuerAdmission.privateIssuerAllowed(). Nothing a caller passes in and nothing a properties file contains can change the answer.What setting it gives up.
ExternalIssuerAdmissionresolves the issuer name and refuses every address in the egress reject set - loopback, RFC 1918, link-local and the cloud metadata address among them - and this client then dials exactly those addresses. A proxy makes all of that inert: the name, not an address, is what reaches the proxy, and where the proxy sends the request is the proxy's decision. TLS still binds the hostname, so the credential is not exposed to the proxy; what is given up is the SSRF and egress screening.- Returns:
trueonly when the variable is present with exactly the enabling value
-
okHttpClient
public okhttp3.OkHttpClient okHttpClient()The underlying client.- Returns:
- the client; never
null, and never the ANKASecure client's
-
issuer
- Returns:
- the issuer this client is pinned to; never
null
-
origin
- Returns:
- the origin this client is pinned to, as
scheme://host:port
-
close
public void close()Releases this client's connection pool and dispatcher.Idempotent, and it does not interrupt a call in flight - it evicts idle connections and lets the dispatcher's executor finish what it has. Calls already dispatched still complete; what ends is the pool holding a connection to somebody else's authorization server open after the session that needed it is over.
The pool is the half that always has something to release. The dispatcher's executor is created lazily by OkHttp and every call this SDK makes on this client is synchronous, so on that path the shutdown is over an executor with no thread in it; it matters for a caller that enqueued on the
okHttpClient()this class hands out.- Specified by:
closein interfaceAutoCloseable
-
toString
-