Skip to content
This repository has been archived by the owner on May 3, 2020. It is now read-only.

Refactor configuration handling in Config class. #515

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ cert.pem
key.pem
tmp/*
templates/*
log/*.log
attachments/*
config.json
plugins/*
Expand Down
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM ruby:2.3.5
MAINTAINER Serpico

ENV SRP_ROOT /Serpico
WORKDIR $SRP_ROOT
COPY . $SRP_ROOT

RUN bundle install

# Allow DB to be on a shared volume
VOLUME ["$SRP_ROOT/db"]
EXPOSE 8443

CMD ["bash", "scripts/docker.sh"]
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
source 'https://rubygems.org'

ruby "2.3.3"
ruby "2.3.5"

gem 'sinatra'
gem 'haml'
Expand Down
7 changes: 6 additions & 1 deletion config.json.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
"Logging and Auditing",
"Imported"
],
"finding_states": [
"Draft",
"Under Review",
"Completed"
],
"logo": "/img/logo_1.svg",
"auto_import": false,
"chart": true,
Expand All @@ -47,4 +52,4 @@
"AES128-GCM-SHA256","AES256-GCM-SHA384","AES128-SHA256",
"AES256-SHA256","AES128-SHA","AES256-SHA"],
"languages":["English"]
}
}
37 changes: 37 additions & 0 deletions config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require 'json'

class Config
private_class_method :new # Don't allow instantiation.
@config = nil
def self._get_config()
@defaults = JSON.parse(File.read('./config.json.defaults'))
@config = JSON.parse(File.read('./config.json'))
end

def self._get(key)
Config._get_config() unless @config
return @config[key] if @config.key?(key)
return @defaults[key] # Fallback to default values
end

def self.[](key) return Config._get(key) end

def self.[]=(key, val)
Config._get_config() unless @config
@config[key] = val
end

def self.key?(key)
Config._get_config() unless @config
return @config.key?(key)
end

def self.each(&block)
@config.each(&block)
end

def self.to_json(*a)
return @config.to_json
end
end

13 changes: 13 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: '3'
services:
serpico:
environment:
- SRP_ADMIN=administrator
- SRP_FINDINGS=yes
build:
dockerfile: docker/dev.dockerfile
context: .
ports:
- "8443:8443"
volumes:
- ./:/Serpico
9 changes: 9 additions & 0 deletions docker/dev.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM ruby:2.3.5
MAINTAINER Serpico
ENV SRP_ROOT /Serpico
WORKDIR $SRP_ROOT
# No volume: It will be mounted by docker-compose.
COPY Gemfile $SRP_ROOT/
RUN bundle install
EXPOSE 8443
CMD ["bash", "docker/docker.sh"]
12 changes: 12 additions & 0 deletions docker/docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/bash
# This script is used as the entry point for the docker image.
# It will initialize the database if it isn't already present.

if [ ! -f "$SRP_ROOT/db/master.db" ]; then
echo "First run detected. Initializing database..."
ruby "$SRP_ROOT/scripts/first_time.rb"
fi

# CMD ["ruby", "serpico.rb"]
ruby serpico.rb

65 changes: 65 additions & 0 deletions docs/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Running Inside Docker

The included `Dockerfile` allows to run Serpico inside docker from any system
that supports containers.

By default, Serpico listens on 8443, you can expose it as `443` if you would
like by using `docker run -p 443:8443 ...`

The image needs to first be built.

1. Build the image
2. Map the database location in docker-compose or at `docker run` time.
3. If the database doesn't exist, it will be created with defaults

## Creating the image

This will create a container with the current state of your repository.
The database is not created at this point, and this image is safe for
distribution.

```
docker build -t serpico .
```

The Dockerfile exposes a `VOLUME` at `/Serpico/db` to allow mounting an
external database through `docker-compose` or `docker run -v`.


## Running with `docker run`

```
# Create a new container called "serpico" and run it in the foreground
docker run --name serpico -p 8443:8443 -v"$(pwd)":/Serpico/db -it serpico

# Stop the container when you no longer need it
docker stop serpico

# Start it again when you need it. It will keep its state.
docker start serpico
```

This will store the database locally at `$PWD/master.db` Please note that the
path to the database on the host [must be absolute][1].

[1]: https://docs.docker.com/engine/reference/run/#volume-shared-filesystems

## docker-compose

The `docker-compose.yml` in the repository is aimed at development use. It will
provision the ruby environment inside the container and mount the repository as
the docker application, allowing for reloading the source code by simply
restarting the container. The dockerfile `docker/dev.dockerfile` is used by
compose.

## Caveats

This is a work in progress, so a few things are currently not supported.

- Running a new container with an existing `master.db` will not work because
`first_time.rb` will not run, and there won't be any certificates for SSL.
- `config.json` is not exposed to the host so customization requires rebuilding
the image or accessing it with `docker exec bash`.
- `docker-compose up` will not automatically reload the backend when `.rb`
files are changed. This is a possible improvement.

1 change: 1 addition & 0 deletions model/master.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ class Findings
property :nist_rating, String, :required => false

property :language, String, required: false
property :state, Integer, required: false
end

class TemplateReports
Expand Down
123 changes: 61 additions & 62 deletions routes/admin.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
require 'sinatra'
require 'zip'

config_options = JSON.parse(File.read('./config.json'))
require './config'

# set the report_assessment_types for <1.2 versions of Serpico
unless config_options['report_assessment_types']
config_options['report_assessment_types'] = ['Network Internal', 'External', 'Web application', 'Physical', 'Social engineering', 'Configuration audit']
unless Config['report_assessment_types']
Config['report_assessment_types'] = ['Network Internal', 'External', 'Web application', 'Physical', 'Social engineering', 'Configuration audit']
end

######
Expand Down Expand Up @@ -183,16 +182,16 @@
get '/admin/config' do
redirect to('/no_access') unless is_administrator?

@config = config_options
@scoring = if config_options['cvss']
@config = Config
@scoring = if Config['cvss']
'cvss'
elsif config_options['cvssv3']
elsif Config['cvssv3']
'cvssv3'
elsif config_options['dread']
elsif Config['dread']
'dread'
elsif config_options['riskmatrix']
elsif Config['riskmatrix']
'riskmatrix'
elsif config_options["nist800"]
elsif Config["nist800"]
'nist800'
else
'default'
Expand All @@ -209,68 +208,68 @@
rat = params['report_assessment_types'].split(',')
lang = params['languages'].delete(' ').split(',')

config_options['effort'] = params['effort'].split(',') if params['effort']

config_options['finding_types'] = ft
config_options['user_defined_variables'] = udv
config_options['port'] = params['port']
config_options['report_assessment_types'] = rat
config_options['languages'] = lang
config_options['use_ssl'] = params['use_ssl'] ? true : false
config_options['bind_address'] = params['bind_address']
config_options['ldap'] = params['ldap'] ? true : false
config_options['ldap_domain'] = params['ldap_domain']
config_options['ldap_dc'] = params['ldap_dc']
config_options['burpmap'] = params['burpmap'] ? true : false
config_options['nessusmap'] = params['nessusmap'] ? true : false
config_options['vulnmap'] = params['vulnmap'] ? true : false
config_options['logo'] = params['logo']
config_options['auto_import'] = params['auto_import'] ? true : false
config_options['chart'] = params['chart'] ? true : false
config_options['threshold'] = params['threshold']
config_options['show_exceptions'] = params['show_exceptions'] ? true : false
config_options['cvssv2_scoring_override'] = params['cvssv2_scoring_override'] ? true : false
Config['effort'] = params['effort'].split(',') if params['effort']

Config['finding_types'] = ft
Config['user_defined_variables'] = udv
Config['port'] = params['port']
Config['report_assessment_types'] = rat
Config['languages'] = lang
Config['use_ssl'] = params['use_ssl'] ? true : false
Config['bind_address'] = params['bind_address']
Config['ldap'] = params['ldap'] ? true : false
Config['ldap_domain'] = params['ldap_domain']
Config['ldap_dc'] = params['ldap_dc']
Config['burpmap'] = params['burpmap'] ? true : false
Config['nessusmap'] = params['nessusmap'] ? true : false
Config['vulnmap'] = params['vulnmap'] ? true : false
Config['logo'] = params['logo']
Config['auto_import'] = params['auto_import'] ? true : false
Config['chart'] = params['chart'] ? true : false
Config['threshold'] = params['threshold']
Config['show_exceptions'] = params['show_exceptions'] ? true : false
Config['cvssv2_scoring_override'] = params['cvssv2_scoring_override'] ? true : false

if params['risk_scoring'] == 'CVSSv2'
config_options['dread'] = false
config_options['cvss'] = true
config_options['cvssv3'] = false
config_options['riskmatrix'] = false
config_options['nist800'] = false
Config['dread'] = false
Config['cvss'] = true
Config['cvssv3'] = false
Config['riskmatrix'] = false
Config['nist800'] = false
elsif params['risk_scoring'] == 'CVSSv3'
config_options['dread'] = false
config_options['cvss'] = false
config_options['cvssv3'] = true
config_options['riskmatrix'] = false
config_options['nist800'] = false
Config['dread'] = false
Config['cvss'] = false
Config['cvssv3'] = true
Config['riskmatrix'] = false
Config['nist800'] = false
elsif params['risk_scoring'] == 'DREAD'
config_options['dread'] = true
config_options['cvss'] = false
config_options['cvssv3'] = false
config_options['riskmatrix'] = false
config_options['nist800'] = false
Config['dread'] = true
Config['cvss'] = false
Config['cvssv3'] = false
Config['riskmatrix'] = false
Config['nist800'] = false
elsif params['risk_scoring'] == 'RISKMATRIX'
config_options['dread'] = false
config_options['cvss'] = false
config_options['cvssv3'] = false
config_options['riskmatrix'] = true
config_options['nist800'] = false
Config['dread'] = false
Config['cvss'] = false
Config['cvssv3'] = false
Config['riskmatrix'] = true
Config['nist800'] = false
elsif params['risk_scoring'] == 'NIST800-30'
config_options['dread'] = false
config_options['cvss'] = false
config_options['cvssv3'] = false
config_options['riskmatrix'] = false
config_options['nist800'] = true
Config['dread'] = false
Config['cvss'] = false
Config['cvssv3'] = false
Config['riskmatrix'] = false
Config['nist800'] = true
else
config_options['dread'] = false
config_options['cvss'] = false
config_options['cvssv3'] = false
config_options['riskmatrix'] = false
config_options['nist800'] = false
Config['dread'] = false
Config['cvss'] = false
Config['cvssv3'] = false
Config['riskmatrix'] = false
Config['nist800'] = false
end

File.open('./config.json', 'w') do |f|
f.write(JSON.pretty_generate(config_options))
f.write(JSON.pretty_generate(Config))
serpico_log("Configuration file modified")
end
redirect to('/admin/config')
Expand Down
2 changes: 1 addition & 1 deletion routes/api.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
require 'sinatra'
require './config'

##### Simple API Components - Read-Only for now

config_options = JSON.parse(File.read('./config.json'))

# returns an API session key
post '/v1/session' do
Expand Down
9 changes: 4 additions & 5 deletions routes/basic.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
require 'sinatra'
require './config'
### Basic Routes

config_options = JSON.parse(File.read('./config.json'))

# Used for 404 responses
not_found do
"Sorry, I don't know this page."
Expand Down Expand Up @@ -136,14 +135,14 @@

end
elsif user
if config_options['ldap'].to_s == 'true'
if Config['ldap'].to_s == 'true'
# try AD authentication
usern = params[:username]
data = url_escape_hash(request.POST)
redirect to('/') if (usern == '') || (params[:password] == '')

user = "#{config_options['ldap_domain']}\\#{data['username']}"
ldap = Net::LDAP.new host: (config_options['ldap_dc']).to_s, port: 636, encryption: :simple_tls, auth: { method: :simple, username: user, password: params[:password] }
user = "#{Config['ldap_domain']}\\#{data['username']}"
ldap = Net::LDAP.new host: (Config['ldap_dc']).to_s, port: 636, encryption: :simple_tls, auth: { method: :simple, username: user, password: params[:password] }

if ldap.bind
# replace the session in the session table
Expand Down
Loading