Skip to content

Commit

Permalink
Merge pull request #19 from suprjinx/jbuilder
Browse files Browse the repository at this point in the history
Jbuilder for json view, change to request params struct
  • Loading branch information
suprjinx authored Sep 4, 2024
2 parents aed50a1 + 43de778 commit c6f54a0
Show file tree
Hide file tree
Showing 13 changed files with 67 additions and 54 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ gem "vault"
# Use the jwt gem to decode access tokens
gem "jwt"

# Use the jbuilder gem
gem "jbuilder"

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri mswin ], require: "debug/prelude"
Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ GEM
irb (1.14.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jbuilder (2.12.0)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
json (2.7.2)
jwt (2.8.2)
base64
Expand Down Expand Up @@ -272,6 +275,7 @@ DEPENDENCIES
brakeman
debug
interactor (~> 3.0)
jbuilder
jwt
puma (>= 5.0)
rails (~> 7.2.1)
Expand Down
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ Astral is an api-only application intended to simplify
certificate acquisition for other applications/services. Broadly speaking,
it will:

1) Authorize the request for cerficate using a third party trusted source (JWT, etc)
2) If authorized, obtain a certificate from PKI CLM (such as Vault/OpenBao)
3) Log this transaction in audit infrastructure (ELK, etc).
1) Authenticate the request for cerficate using a third party trusted source (JWT, etc)
2) Authorize the request using a Domain Ownership Registry
3) If authorized, obtain a certificate from PKI CLM (such as Vault/OpenBao)
4) Log this transaction in audit infrastructure (ELK, etc).

# Running
# Running in development

This Rails app is most easily run and developed in its devcontainer.

Expand All @@ -22,9 +23,19 @@ rails s
curl -X POST http://localhost:3000/certificates \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huLmRvZUBleGFtcGxlLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMiwiZ3JvdXBzIjpbImdyb3VwMSIsImdyb3VwMiJdLCJhdWQiOiJhc3RyYWwifQ.tfRLXmE_eq-piP88_clwPWrYfMAQbCJAeZQI6OFxZSI" \
-H "Content-type: application/json" \
-d "{ \"common_name\": \"example.com\" }"
-d "{ \"cert_issue_request\": { \"common_name\": \"example.com\" } }"
```
4) Run the tests from devcontainer terminal:
```
rails test
```

# Running the prod image
1) Build the prod image:
```
docker build -t astral:latest .
```
2) Run the prod image:
```
docker run -e SECRET_KEY_BASE=mysecrit -p 3000:3000 astral:latest
```
13 changes: 5 additions & 8 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
class ApplicationController < ActionController::API
before_action :set_default_format
rescue_from StandardError, with: :handle_standard_error
rescue_from AuthError, with: :handle_auth_error
rescue_from ActionController::ParameterMissing, with: :handle_bad_request

attr_reader :identity # decoded and verified JWT

def info
render json: {
app: "astral",
description: "Astral provides a simplified API for PKI.",
version: "0.0.1"
}
end

def authenticate_request
result = AuthenticateIdentity.call(request: request)
if result.success?
Expand All @@ -24,6 +17,10 @@ def authenticate_request

private

def set_default_format
request.format = :json
end

def handle_standard_error(exception)
render json: { error: exception.message }, status: :internal_server_error
end
Expand Down
27 changes: 5 additions & 22 deletions app/controllers/certificates_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,19 @@ def create
req = CertIssueRequest.new(params_permitted)
if !req.valid?
render json: { error: req.errors }, status: :bad_request
return
end
result = IssueCert.call(request: req)
if result.success?
# TODO use jbuilder to make the json
render json: result.cert
else
if result.failure?
raise StandardError.new result.message
end
@cert = result.cert
end

private

def params_permitted
attrs = %i[ common_name
alt_names
exclude_cn_from_sans
format
not_after
other_sans
private_key_format
remove_roots_from_chain
ttl
uri_sans
ip_sans
serial_number
client_flag
code_signing_flag
email_protection_flag
server_flag
]
params.permit(attrs)
attrs = CertIssueRequest.new.attributes.keys
params.require(:cert_issue_request).permit(attrs)
end
end
4 changes: 4 additions & 0 deletions app/controllers/info_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class InfoController < ApplicationController
def index
end
end
9 changes: 3 additions & 6 deletions app/lib/services/vault_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ def initialize
end

def issue_cert(cert_issue_request)
opts = cert_issue_request.attributes
# Generate the TLS certificate using the intermediate CA
tls_cert = @client.logical.write(Rails.application.config.astral[:vault_cert_path],
common_name: cert_issue_request.common_name,
ttl: cert_issue_request.ttl,
ip_sans: cert_issue_request.ip_sans,
format: cert_issue_request.format)
tls_cert.data
tls_cert = @client.logical.write(Rails.application.config.astral[:vault_cert_path], opts)
OpenStruct.new tls_cert.data
end
end
end
16 changes: 10 additions & 6 deletions app/models/cert_issue_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ class CertIssueRequest
include ActiveModel::Attributes

attribute :common_name, :string
attribute :alt_names, array: :string, default: []
attribute :alt_names, :string
attribute :exclude_cn_from_sans, :boolean, default: false
attribute :format, :string, default: "pem"
attribute :not_after, :datetime
attribute :other_sans, array: :string, default: []
attribute :other_sans, :string
attribute :private_key_format, :string, default: "pem"
attribute :remove_roots_from_chain, :boolean, default: false
attribute :ttl, :integer, default: Rails.configuration.astral[:cert_ttl]
attribute :uri_sans, array: :string, default: []
attribute :ip_sans, array: :string, default: []
attribute :uri_sans, :string
attribute :ip_sans, :string
attribute :serial_number, :integer
attribute :client_flag, :boolean, default: true
attribute :code_signing_flag, :boolean, default: false
Expand All @@ -29,15 +29,19 @@ class CertIssueRequest
validate :validate_no_wildcards

def fqdns
alt_names + [ common_name ]
alt_names_array + [ common_name ]
end

def validate_no_wildcards
if common_name.present?
errors.add(:common_name, "cannot be a wildcard") if common_name.start_with? "*"
end
alt_names.each do |fqdn|
alt_names_array.each do |fqdn|
errors.add(:alt_names, "cannot include a wildcard") if fqdn.start_with? "*"
end
end

def alt_names_array
(alt_names || "").split(",")
end
end
7 changes: 7 additions & 0 deletions app/views/certificates/create.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
json.ca_chain @cert.ca_chain
json.certificate @cert.certificate
json.expiration @cert.expiration
json.issuing_ca @cert.issuing_ca
json.private_key @cert.private_key
json.private_key_type @cert.private_key_format
json.serial_number @cert.serial_number
3 changes: 3 additions & 0 deletions app/views/info/index.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
json.app "astral"
json.description "Astral provides a simplified API for PKI."
json.version "0.0.1"
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
get "manifest" => "rails/pwa#manifest", as: :pwa_manifest

# Defines the root path route ("/")
root "application#info"
root "info#index"

resources :certificates, only: %i[create]
end
2 changes: 1 addition & 1 deletion test/integration/certificates_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class CertificatesControllerTest < ActionDispatch::IntegrationTest
test "#create authorized" do
jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huLmRvZUBleGFtcGxlLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMiwiZ3JvdXBzIjpbImdyb3VwMSIsImdyb3VwMiJdLCJhdWQiOiJhc3RyYWwifQ.tfRLXmE_eq-piP88_clwPWrYfMAQbCJAeZQI6OFxZSI"
post certificates_path, headers: { "Authorization" => "Bearer #{jwt}" },
params: { common_name: "example.com" }
params: { cert_issue_request: { common_name: "example.com" } }
assert_response :success
%w[ ca_chain
certificate
Expand Down
10 changes: 5 additions & 5 deletions test/models/cert_isssue_request_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ class CertIssueRequestTest < ActiveSupport::TestCase
def setup
@attributes = {
common_name: "example.com",
alt_names: [ "alt1.example.com", "alt2.example.com" ],
alt_names: "alt1.example.com,alt2.example.com",
exclude_cn_from_sans: true,
format: "der",
not_after: DateTime.now + 1.year,
other_sans: [ "other1", "other2" ],
other_sans: "other1,other2",
private_key_format: "pkcs8",
remove_roots_from_chain: true,
ttl: 365,
uri_sans: [ "http://example.com" ],
ip_sans: [ "192.168.1.1" ],
uri_sans: "http://example.com",
ip_sans: "192.168.1.1",
serial_number: 123456,
client_flag: false,
code_signing_flag: true,
Expand Down Expand Up @@ -71,7 +71,7 @@ def setup
end

test "#valid? should prevent wildcard alt_names" do
@cert_issue_request.alt_names = [ "www.example.com", "*.example.com" ]
@cert_issue_request.alt_names = "www.example.com,*.example.com"
assert_not @cert_issue_request.valid?
assert_includes @cert_issue_request.errors[:alt_names], "cannot include a wildcard"
end
Expand Down

0 comments on commit c6f54a0

Please sign in to comment.