Answer
To harden a Ballerina microservice using only the language’s built‑in security constructs, follow these steps. They assume Ballerina ≥ 2201.0.0 (the version that introduced the @Authorization and @Scope annotations).
1. Declare an authentication scheme
import ballerina/http;
import ballerina/jwt;
// Example: OAuth2 JWT validator
@http:SecurityScheme {
scheme: "oauth2",
grantType: "client_credentials",
tokenEndpoint: "https://auth.example.com/token",
// Public key or JWKS URI for signature verification
jwtValidatorConfig: { jwksUrl: "https://auth.example.com/.well-known/jwks.json" }
}
serviceAuthScheme;
2. Bind the scheme to the listener (network exposure)
@http:ListenerConfig {
host: "0.0.0.0", // bind only to needed interface
port: 9090,
secureSocket: { // enable mTLS if mutual auth is required
enabled: true,
cert: {{ certFile: "cert.pem", keyFile: "key.pem" }},
ca: {{ caFile: "ca.pem" }},
clientAuth: "require"
}
}
service /api on new http:Listener(9090, config = listenerConf) {
attach serviceAuthScheme;
// ...
}
3. Apply fine‑grained authorization to resources
@http:Service { basePath: "/orders" }
service OrdersService on new http:Listener(9090) {
@http:Resource {
methods: ["GET"],
path: "/{id}",
// Requires the read:orders scope
@Authorization { scopes: ["read:orders"] }
@Scope { value: "read:orders" }
}
function getOrder(string id) returns Order|error {
// Business logic – only reachable if token contains read:orders
...
}
@http:Resource {
methods: ["POST"],
path: "/",
@Authorization { scopes: ["write:orders"] }
@Scope { value: "write:orders" }
}
function createOrder(Order payload) returns Order|error {
...
}
}
4. Validate and use JWT claims (optional extra checks)
import ballerina/jwt;
@http:Resource { methods: ["GET"], path: "/admin" }
@Authorization { scopes: ["admin:orders"] }
function adminGet() returns string|error {
// The JWT payload is available via the built‑in $jwt variable
// Example: ensure the token was issued for a specific audience
if jwt:$jwt.aud != "orders-service" {
return error("Invalid audience");
}
return "Admin data";
}
5. Limit runtime privileges (deployment‑concern)
- User: Run the Ballerina executable as a non‑root OS user (e.g.,
ballerina:ballerina user in a Dockerfile).
- File system: Avoid unnecessary
ballerina/file calls. If file access is required, restrict paths to a dedicated directory and set OS‑level permissions (read‑only where possible).
- Network outbound: Only create outbound HTTP clients when needed, and configure them with explicit target hosts/ports. Block all other outbound traffic via container/network policies.
Note: If you are deploying to Kubernetes, verify whether you can set a securityContext to drop capabilities and run as a non‑root user; this step changes the recommendation because the language‑level controls do not replace pod‑level privileges.