You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
382 lines
15 KiB
382 lines
15 KiB
error_log stderr ${LOG_LEVEL};
|
|
|
|
worker_processes 1;
|
|
|
|
events {
|
|
worker_connections 1024;
|
|
}
|
|
|
|
http {
|
|
default_type text/plain;
|
|
sendfile on;
|
|
keepalive_timeout 65;
|
|
resolver ${RESOLVER} ipv6=off;
|
|
|
|
server {
|
|
listen 8443 ssl;
|
|
server_name ${PROXY_ROUTE_HOSTNAME};
|
|
|
|
ssl on;
|
|
ssl_certificate ${APP_ROOT}/etc/serving-cert/tls.crt;
|
|
ssl_certificate_key ${APP_ROOT}/etc/serving-cert/tls.key;
|
|
|
|
# Enable SSL/TLS Client Certificates Authentication
|
|
ssl_verify_client optional;
|
|
ssl_client_certificate ${APP_ROOT}/etc/ca-certs/ca-bundle.pem;
|
|
ssl_crl ${APP_ROOT}/etc/ca-certs/crl.pem;
|
|
|
|
location ~ ^/auth/realms/(${SSO_REALMS})/register$ {
|
|
access_by_lua_block {
|
|
ngx.log(ngx.INFO, "VERIFY: ", ngx.var.ssl_client_verify)
|
|
if ngx.var.ssl_client_verify ~= "SUCCESS" then
|
|
ngx.status = ngx.HTTP_FORBIDDEN
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"You need to authenticate using an SSL/TLS Client Certificate."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
ngx.log(ngx.INFO, "Authenticated Client : ", ngx.var.ssl_client_s_dn)
|
|
}
|
|
|
|
content_by_lua_block {
|
|
local realm = ngx.var[1]; -- captured from the location regex
|
|
ngx.log(ngx.INFO, "Received a registration request for realm " .. realm)
|
|
if ngx.req.get_method() ~= "POST" then
|
|
ngx.status = ngx.HTTP_NOT_ALLOWED
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"Only POST requests are accepted."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
if ngx.req.get_headers()["Content-Type"] ~= "application/x-www-form-urlencoded" then
|
|
ngx.status = 415 -- Unsupported Media
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"Wrong content-type. Must be application/x-www-form-urlencoded."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
ngx.req.read_body()
|
|
local args, err = ngx.req.get_post_args()
|
|
if not args then
|
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"No post parameters found."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
if not args['apikey'] or args['apikey'] == '' then
|
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"No apikey parameter found in the request."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
if not args['client_id'] or args['client_id'] == '' then
|
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"No client_id parameter found in the request."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
local apikey = args['apikey']
|
|
local client_id = args['client_id']
|
|
|
|
local http = require "resty.http"
|
|
local httpc = http.new()
|
|
|
|
local res, err = httpc:request_uri("${BACKEND_ENDPOINT_OVERRIDE}/transactions/authrep.xml", {
|
|
query = {
|
|
service_token = "${THREESCALE_SERVICE_TOKEN}",
|
|
service_id = "${THREESCALE_SERVICE_ID}",
|
|
user_key = apikey
|
|
},
|
|
method = "GET",
|
|
ssl_verify = false
|
|
})
|
|
|
|
if not res then
|
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"server_error","error_description":"Could not get a response from the 3scale backend."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
if res.status ~= ngx.HTTP_OK then
|
|
ngx.status = ngx.HTTP_FORBIDDEN
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"server_error","error_description":"Denied by the 3scale backend."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
res, err = httpc:request_uri("${THREESCALE_PORTAL_ENDPOINT}/admin/api/applications/find.json", {
|
|
query = {
|
|
access_token = "${THREESCALE_ACCESS_TOKEN}",
|
|
user_key = apikey
|
|
},
|
|
method = "GET",
|
|
ssl_verify = false
|
|
})
|
|
|
|
if not res then
|
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"server_error","error_description":"Could not get a response from the 3scale API."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
local cjson = require 'cjson'
|
|
local json_body = cjson.decode(res.body)
|
|
local apikey_account_id = json_body['application'] and json_body['application']['account_id']
|
|
|
|
res, err = httpc:request_uri("${THREESCALE_PORTAL_ENDPOINT}/admin/api/applications/find.json", {
|
|
query = {
|
|
access_token = "${THREESCALE_ACCESS_TOKEN}",
|
|
app_id = client_id
|
|
},
|
|
method = "GET",
|
|
ssl_verify = false
|
|
})
|
|
|
|
if not res then
|
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"server_error","error_description":"Could not get a response from the 3scale API."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
json_body = cjson.decode(res.body)
|
|
local client_account_id = json_body['application'] and json_body['application']['account_id']
|
|
|
|
if client_account_id ~= apikey_account_id then
|
|
ngx.status = ngx.HTTP_FORBIDDEN
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"The apikey and client_id do not belong to the same account."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
res, err = httpc:request_uri("http://${SSO_SERVICE_HOSTNAME}/auth/realms/"..realm.."/protocol/openid-connect/token", {
|
|
body = ngx.encode_args({
|
|
client_id = "${SSO_CLIENT_ID}",
|
|
username = "${SSO_SERVICE_USERNAME}",
|
|
password = "${SSO_SERVICE_PASSWORD}",
|
|
grant_type = "password"
|
|
}),
|
|
headers = {
|
|
["Content-Type"] = "application/x-www-form-urlencoded",
|
|
["Host"] = ngx.var.host
|
|
},
|
|
method = "POST",
|
|
ssl_verify = false
|
|
})
|
|
|
|
if not res or res.status ~= ngx.HTTP_OK then
|
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"server_error","error_description":"Could not get an access token with admin privileges on RH-SSO."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
json_body = cjson.decode(res.body)
|
|
local access_token = json_body['access_token']
|
|
|
|
res, err = httpc:request_uri("http://${SSO_SERVICE_HOSTNAME}/auth/admin/realms/"..realm.."/clients", {
|
|
query = {
|
|
clientId = client_id
|
|
},
|
|
headers = {
|
|
["Authorization"] = "Bearer " .. access_token,
|
|
["Host"] = ngx.var.host
|
|
},
|
|
method = "GET",
|
|
ssl_verify = false
|
|
})
|
|
|
|
if not res or res.status ~= ngx.HTTP_OK then
|
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"server_error","error_description":"Could not retrieve the list of clients in RH-SSO."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
json_body = cjson.decode(res.body)
|
|
local client_id_rhssoid = json_body[1] and json_body[1]['id']
|
|
|
|
if not client_id_rhssoid then
|
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"server_error","error_description":"Could not find the client in RH-SSO."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
res, err = httpc:request_uri("http://${SSO_SERVICE_HOSTNAME}/auth/admin/realms/"..realm.."/clients/" .. client_id_rhssoid .. "/certificates/jwt.credential", {
|
|
method = "GET",
|
|
headers = {
|
|
["Authorization"] = "Bearer " .. access_token,
|
|
["Host"] = ngx.var.host
|
|
},
|
|
ssl_verify = false
|
|
})
|
|
|
|
if not res or res.status ~= ngx.HTTP_OK then
|
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"server_error","error_description":"Could not check client certificates in RH-SSO."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
json_body = cjson.decode(res.body)
|
|
local existing_certificate = json_body['certificate']
|
|
|
|
if existing_certificate then
|
|
ngx.status = ngx.HTTP_FORBIDDEN
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"Already registered."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
local certificate = ngx.var.ssl_client_raw_cert
|
|
local start, len = string.find(certificate, "[-]+BEGIN CERTIFICATE[-]+")
|
|
start = start + len
|
|
local finish = string.find(certificate, "[-]+END CERTIFICATE[-]+")
|
|
certificate = string.sub(certificate, start, finish-1)
|
|
|
|
local raw_certificate = ""
|
|
for i in string.gmatch(certificate, "[a-zA-Z0-9+/=]+") do
|
|
raw_certificate = raw_certificate .. i
|
|
end
|
|
|
|
local boundary = string.format("--------------------------%s", ngx.var.request_id)
|
|
local content_type = "multipart/form-data; boundary=" .. boundary
|
|
|
|
body = string.format('--%s\r\nContent-Disposition: form-data; name="file"\r\nContent-Type: application/octet-stream\r\n\r\n%s\r\n--%s\r\nContent-Disposition: form-data; name="keystoreFormat"\r\n\r\n%s\r\n--%s--\r\n', boundary, raw_certificate, boundary, "Certificate PEM", boundary)
|
|
ngx.say(body)
|
|
res, err = httpc:request_uri("http://${SSO_SERVICE_HOSTNAME}/auth/admin/realms/"..realm.."/clients/" .. client_id_rhssoid .. "/certificates/jwt.credential/upload", {
|
|
method = "POST",
|
|
body = body,
|
|
headers = {
|
|
["Authorization"] = "Bearer " .. access_token,
|
|
["Content-Type"] = content_type,
|
|
["Host"] = ngx.var.host
|
|
},
|
|
ssl_verify = false
|
|
})
|
|
|
|
if not res or res.status ~= ngx.HTTP_OK then
|
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"server_error","error_description":"Could not register certificate in RH-SSO."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
ngx.status = ngx.HTTP_OK
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{ "status": "registered" }')
|
|
|
|
ngx.exit(ngx.HTTP_OK)
|
|
}
|
|
}
|
|
|
|
location ~ ^/auth/realms/(${SSO_REALMS})/protocol/openid-connect/token$ {
|
|
access_by_lua_block {
|
|
ngx.log(ngx.INFO, "VERIFY: ", ngx.var.ssl_client_verify)
|
|
if ngx.var.ssl_client_verify ~= "SUCCESS" or not ngx.var.ssl_client_raw_cert then
|
|
ngx.status = ngx.HTTP_FORBIDDEN
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"You need to authenticate using an SSL/TLS Client Certificate."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
ngx.log(ngx.INFO, "Authenticated Client : ", ngx.var.ssl_client_s_dn)
|
|
}
|
|
|
|
content_by_lua_block {
|
|
local jwt = require "resty.jwt"
|
|
local http = require "resty.http"
|
|
|
|
ngx.req.read_body()
|
|
local form, err = ngx.req.get_post_args()
|
|
if not form then
|
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"You need to pass the token request arguments in the post body."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
local client_assertion = form['client_assertion']
|
|
local client_assertion_type = form['client_assertion_type']
|
|
if not client_assertion_type or client_assertion_type ~= "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" then
|
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"Only JWT is allowed for client authentication. See RFC 7523."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
if not client_assertion then
|
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"No client_assertion found."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
local certificate = ngx.var.ssl_client_raw_cert
|
|
local jwt_obj = jwt:load_jwt(client_assertion)
|
|
if not jwt_obj.valid then
|
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"client_assertion is not a valid JWT."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
if jwt_obj.header.alg ~= "RS256" then
|
|
ngx.status = ngx.HTTP_BAD_REQUEST
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"client_assertion must be signed using the RS256 algorithm."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
jwt_obj = jwt:verify_jwt_obj(certificate, jwt_obj, {})
|
|
if not jwt_obj.verified then
|
|
ngx.status = ngx.HTTP_FORBIDDEN
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"invalid_request","error_description":"client_assertion signature cannot be verified."}')
|
|
ngx.log(ngx.WARN, "JWT validation error: ", jwt_obj.reason)
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
local httpc = http.new()
|
|
|
|
local body = ngx.req.get_body_data()
|
|
local headers = {}
|
|
local h = ngx.req.get_headers()
|
|
for k, v in pairs(h) do
|
|
headers[k] = v
|
|
end
|
|
local res, err = httpc:request_uri("http://${SSO_SERVICE_HOSTNAME}" .. ngx.var.uri, {
|
|
body = body,
|
|
headers = headers,
|
|
method = "POST",
|
|
ssl_verify = false
|
|
})
|
|
|
|
if not res then
|
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
|
|
ngx.header['Content-Type'] = 'application/json'
|
|
ngx.say('{"error":"server_error","error_description":"Could not get a response from RH-SSO."}')
|
|
ngx.exit(ngx.HTTP_OK)
|
|
end
|
|
|
|
ngx.status = res.status;
|
|
ngx.header['Content-Type'] = res.headers['Content-Type']
|
|
ngx.say(res.body);
|
|
}
|
|
}
|
|
|
|
location ~ ^/.* {
|
|
# Beware: no slash at the end of the proxy_pass directive since we don't want to rewrite URLs
|
|
proxy_pass http://${SSO_SERVICE_HOSTNAME};
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
|
|
}
|
|
}
|
|
|