Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate apps with SecTester #743

Merged
merged 25 commits into from
Apr 6, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5fa84d9
inital setup to adding in the sec tester. Fixes #740
jwoertink Mar 19, 2022
7da39fa
updating the flow security_spec with some clarity for a few tests
jwoertink Mar 27, 2022
41e65ee
Updating the generated CI config to include running the sec tests.
jwoertink Mar 27, 2022
ac63606
typo and some docs
jwoertink Mar 27, 2022
e3f2f75
flip flop the setup and ignore the crystal version so hopefully specs…
jwoertink Mar 28, 2022
f5f6f78
Adding in the integration spec to test the sec_tests.
jwoertink Mar 30, 2022
258c373
actually this should run the sec tests
jwoertink Mar 30, 2022
20ab2a9
Oops... point to the correct CI file
jwoertink Apr 1, 2022
e347017
ensure we're always in the project directory
jwoertink Apr 1, 2022
0fc43d2
merged main. Fixed conflict in spec
jwoertink Apr 1, 2022
c11c9e4
run all the setup and stuff in the right place with the right files
jwoertink Apr 2, 2022
451a5e6
the global CI needs nexplot so it just exists everywhere
jwoertink Apr 2, 2022
563467e
make sure the weekly runs the nexploit repeater
jwoertink Apr 2, 2022
b9f79de
These workflows are expensive enough. We don't need them running ever…
jwoertink Apr 2, 2022
92222d4
make sure only one instance of the scanner is used per spec
jwoertink Apr 2, 2022
ed8b509
Not everyone will be able to run the nexploit checks locally. Moving …
jwoertink Apr 2, 2022
8808a3d
no clue how I managed that....
jwoertink Apr 2, 2022
f28f949
SSL is off during specs which will cause the cookie check to fail, so…
jwoertink Apr 3, 2022
1784c7d
make this an enum
jwoertink Apr 3, 2022
31e058f
debugging to hopefully see what's up
jwoertink Apr 4, 2022
c4f33a1
See if we can just use the node version already installed
jwoertink Apr 4, 2022
06e5ad4
Merge branch 'main' into issues/740
jwoertink Apr 4, 2022
0f123dd
make sure that the weekly CI is setup correctly for SecTester. Updati…
jwoertink Apr 5, 2022
5d39f40
merge conflict
jwoertink Apr 5, 2022
7d306c5
make sure to catch the formatter so generated CIs don't fail
jwoertink Apr 5, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Install npm and Nexploit Repeater
run: |
sudo npm install -g @neuralegion/nexploit-cli --unsafe-perm=true
- name: Run setup script
run: ./script/setup
- name: Install Lucky CLI
Expand All @@ -91,3 +94,5 @@ jobs:
crystal spec
env:
LUCKY_ENV: test
NEXPLOIT_TOKEN: ${{ secrets.BRIGHT_API_KEY }}
RUN_SEC_TESTER_SPECS: 1
6 changes: 3 additions & 3 deletions .github/workflows/weekly.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
name: Lucky CLI Weekly CI

on:
push:
branches: [main]
pull_request:
Comment on lines -4 to -6
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was causing this to run on every single commit. It should be a separate PR, but I needed it to quiet down with all the testing.

schedule:
- cron: "0 1 * * MON"
workflow_dispatch:
Expand Down Expand Up @@ -44,6 +41,9 @@ jobs:
- uses: actions/setup-node@v3.0.0
with:
node-version: "12.x"
- name: Install npm and Nexploit Repeater
run: |
sudo npm install -g @neuralegion/nexploit-cli --unsafe-perm=true
- name: Run setup script
run: ./script/setup
- name: Install Lucky CLI
Expand Down
21 changes: 3 additions & 18 deletions spec/integration/init_web_spec.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "../spec_helper"

include ShouldRunSuccessfully
include WithProjectCleanup

describe "Initializing a new web project" do
it "creates a full web app successfully" do
Expand All @@ -16,7 +17,8 @@ describe "Initializing a new web project" do

File.delete("test-project/.env")
compile_and_run_specs_on_test_project
File.read(".github/workflows/ci.yml").should contain "postgres"
File.read("test-project/Procfile").should contain "bin/app"
File.read("test-project/.github/workflows/ci.yml").should contain "postgres"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

turns out this has been wrong for a while 😅

File.read("test-project/public/mix-manifest.json").should contain "images/cat.gif"
File.exists?("test-project/public/favicon.ico").should eq true
File.exists?("test-project/.env").should eq true
Expand Down Expand Up @@ -139,20 +141,3 @@ private def compile_and_run_specs_on_test_project
should_run_successfully "crystal spec"
end
end

private def with_project_cleanup(project_directory = "test-project", skip_db_drop = false)
yield

FileUtils.cd(project_directory) {
output = IO::Memory.new
Process.run(
"lucky db.drop",
output: output,
shell: true
)

output.to_s.should contain("Done dropping")
} unless skip_db_drop
ensure
FileUtils.rm_rf project_directory
end
23 changes: 23 additions & 0 deletions spec/integration/sec_tester_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require "../spec_helper"

{% if env("RUN_SEC_TESTER_SPECS") == "1" %}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyone that wants to run this spec locally would need a Bright API key... That's not really going to happen, so we can treat this like the Heroku specs. As long as this spec passes in CI, and the rest pass locally, we should be fine

include ShouldRunSuccessfully
include WithProjectCleanup

describe "Apps with SecTester enabled" do
it "creates a full app with sec_tester enabled" do
puts "Web app with SecTester: Running integration spec. This might take awhile...".colorize(:yellow)
with_project_cleanup do
should_run_successfully "crystal run src/lucky.cr -- init.custom test-project --with-sec-test"

FileUtils.cd "test-project" do
File.read("spec/setup/sec_tester.cr").should contain "LuckySecTester"
File.read(".github/workflows/ci.yml").should contain "-Dwith_sec_tests"
File.read("spec/flows/security_spec.cr").should contain "dom_xss"
should_run_successfully "./script/setup"
should_run_successfully "crystal spec -Dwith_sec_tests"
end
end
end
end
{% end %}
18 changes: 18 additions & 0 deletions spec/support/with_project_cleanup.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module WithProjectCleanup
private def with_project_cleanup(project_directory = "test-project", skip_db_drop = false) : Nil
yield

FileUtils.cd(project_directory) {
output = IO::Memory.new
Process.run(
"lucky db.drop",
output: output,
shell: true
)

output.to_s.should contain("Done dropping")
} unless skip_db_drop
ensure
FileUtils.rm_rf project_directory
end
end
117 changes: 117 additions & 0 deletions src/app_with_sec_tester/spec/flows/security_spec.cr.ecr
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{% skip_file unless flag?(:with_sec_tests) %}
# Run these specs with `crystal spec -Dwith_sec_tests`

require "../spec_helper"

describe "SecTester" do
<%- if browser? -%>
<%- if generate_auth? -%>
it "tests the sign_in for dom based XSS" do
scan_with_cleanup do |scanner|
target = scanner.build_target(SignIns::New)
scanner.run_check(
scan_name: "ref: #{ENV["GITHUB_REF"]?} commit: #{ENV["GITHUB_SHA"]?} run id: #{ENV["GITHUB_RUN_ID"]?}",
tests: "dom_xss",
target: target
)
end
end

it "tests the sign_in page for SQLi, OSI, XSS attacks" do
scan_with_cleanup do |scanner|
target = scanner.build_target(SignIns::Create) do |t|
t.body = "user%3Aemail=test%40test.com&user%3Apassword=1234"
end
scanner.run_check(
scan_name: "ref: #{ENV["GITHUB_REF"]?} commit: #{ENV["GITHUB_SHA"]?} run id: #{ENV["GITHUB_RUN_ID"]?}",
tests: [
"sqli", # Testing for SQL Injection issues (https://docs.neuralegion.com/docs/sql-injection)
"osi",
"xss",
],
target: target
)
end
end

it "tests the sign_up page for dom based XSS" do
scan_with_cleanup do |scanner|
target = scanner.build_target(SignUps::New)
scanner.run_check(
scan_name: "ref: #{ENV["GITHUB_REF"]?} commit: #{ENV["GITHUB_SHA"]?} run id: #{ENV["GITHUB_RUN_ID"]?}",
tests: "dom_xss",
target: target
)
end
end

it "tests the sign_up page for ALL attacks" do
scan_with_cleanup do |scanner|
target = scanner.build_target(SignUps::Create) do |t|
t.body = "user%3Aemail=aa%40aa.com&user%3Apassword=123456789&user%3Apassword_confirmation=123456789"
end
scanner.run_check(
scan_name: "ref: #{ENV["GITHUB_REF"]?} commit: #{ENV["GITHUB_SHA"]?} run id: #{ENV["GITHUB_RUN_ID"]?}",
tests: nil,
target: target
)
end
end
<%- end -%>
it "tests the home page for header, and cookie security issues" do
scan_with_cleanup do |scanner|
target = scanner.build_target(Home::Index)
scanner.run_check(
scan_name: "ref: #{ENV["GITHUB_REF"]?} commit: #{ENV["GITHUB_SHA"]?} run id: #{ENV["GITHUB_RUN_ID"]?}",
severity_threshold: :medium,
tests: [
"header_security",
"cookie_security"
],
target: target
)
end
end

it "tests app.js for 3rd party issues" do
scan_with_cleanup do |scanner|
target = SecTester::Target.new(Lucky::RouteHelper.settings.base_uri + Lucky::AssetHelpers.asset("js/app.js"))
scanner.run_check(
scan_name: "ref: #{ENV["GITHUB_REF"]?} commit: #{ENV["GITHUB_SHA"]?} run id: #{ENV["GITHUB_RUN_ID"]?}",
tests: "retire_js",
target: target
)
end
end
<%- else -%>
<%- if generate_auth? -%>
it "tests the sign_in API for SQLi, and JWT attacks" do
scan_with_cleanup do |scanner|
api_headers = HTTP::Headers{"Content-Type" => "application/json", "Accept" => "application/json"}
target = scanner.build_target(Api::SignIns::Create, headers: api_headers) do |t|
t.body = {"user" => {"email" => "aa%40aa.com", "password" => "123456789"}}.to_json
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwoertink as this is a JSON API call, do we actually need to encode the @ here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably not....

end
scanner.run_check(
scan_name: "ref: #{ENV["GITHUB_REF"]?} commit: #{ENV["GITHUB_SHA"]?} run id: #{ENV["GITHUB_RUN_ID"]?}",
tests: [
"sqli", # Testing for SQL Injection issues (https://docs.neuralegion.com/docs/sql-injection)
"jwt", # Testing JWT usage (https://docs.neuralegion.com/docs/broken-jwt-authentication)
"xss", # Checking for Cross Site Scripting attacks (https://docs.neuralegion.com/docs/reflective-cross-site-scripting-rxss)
"ssrf", # Checking for SSRF (https://docs.neuralegion.com/docs/server-side-request-forgery-ssrf)
"proto_pollution", # Checking for proto pollution based vulnerabilities (https://docs.neuralegion.com/docs/prototype-pollution)
"full_path_disclosure", # Checking for full path disclourse on api error (https://docs.neuralegion.com/docs/full-path-disclosure)
],
target: target
)
end
end
<%- end -%>
<%- end -%>
end

private def scan_with_cleanup : Nil
scanner = LuckySecTester.new
yield scanner
ensure
scanner.try &.cleanup
end
8 changes: 8 additions & 0 deletions src/app_with_sec_tester/spec/setup/sec_tester.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require "lucky_sec_tester"

# Signup for a `NEXTPLOT_TOKEN` at
# [NeuraLegion](https://app.neuralegion.com/signup)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The company name is now called Bright, but as of this comment, the Github org and this URL are still pointed to NeuraLegion... I don't want to confuse people, so this will need to change once the company's name is fully moved over.

# Read more about the SecTester on https://github.com/luckyframework/lucky_sec_tester
LuckySecTester.configure do |setting|
setting.nexploit_token = ENV["NEXPLOIT_TOKEN"]
end
42 changes: 36 additions & 6 deletions src/generators/web.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ class LuckyCli::Generators::Web
include LuckyCli::GeneratorHelpers

getter project_name : String
getter? api_only, generate_auth
getter? api_only, generate_auth, with_sec_tester

def initialize(
project_name : String,
@api_only : Bool,
@generate_auth : Bool,
@with_sec_tester : Bool = false,
project_directory : String = "."
)
Dir.cd(File.expand_path(project_directory))
Expand All @@ -33,6 +34,7 @@ class LuckyCli::Generators::Web
generate_default_crystal_project
rename_shard_target_to_app
add_deps_to_shard_file
add_dev_deps_to_shard_file
remove_generated_src_files
remove_generated_spec_files
remove_default_readme
Expand All @@ -51,6 +53,10 @@ class LuckyCli::Generators::Web
add_browser_authentication_to_src
end

if with_sec_tester?
add_sec_tester_to_src
end

setup_gitignore
remove_default_license
puts <<-TEXT
Expand Down Expand Up @@ -84,7 +90,7 @@ class LuckyCli::Generators::Web
end

private def add_default_lucky_structure_to_src
SrcTemplate.new(project_name, generate_auth: generate_auth?, api_only: api_only?)
SrcTemplate.new(project_name, generate_auth: generate_auth?, api_only: api_only?, with_sec_tester: with_sec_tester?)
.render("./#{project_dir}", force: true)
end

Expand All @@ -105,6 +111,11 @@ class LuckyCli::Generators::Web
BrowserAuthenticationSrcTemplate.new.render("./#{project_dir}", force: true)
end

private def add_sec_tester_to_src
AppWithSecTesterTemplate.new(generate_auth: generate_auth?, browser: browser?)
.render("./#{project_dir}", force: true)
end

private def remove_generated_src_files
remove_default_generated_if_exists("src")
end
Expand Down Expand Up @@ -185,14 +196,33 @@ class LuckyCli::Generators::Web
version: ~> 1.6.0
DEPS_LIST
end
end

if browser?
private def needs_development_dependencies?
browser? || with_sec_tester?
end

private def add_dev_deps_to_shard_file
if needs_development_dependencies?
append_text to: "shard.yml", text: <<-DEPS_LIST
development_dependencies:
lucky_flow:
github: luckyframework/lucky_flow
version: ~> 0.7.3
DEPS_LIST

if browser?
append_text to: "shard.yml", text: <<-DEPS_LIST
lucky_flow:
github: luckyframework/lucky_flow
version: ~> 0.7.3
DEPS_LIST
end

if with_sec_tester?
append_text to: "shard.yml", text: <<-DEPS_LIST
lucky_sec_tester:
github: luckyframework/lucky_sec_tester
branch: main
DEPS_LIST
end
end
end

Expand Down
5 changes: 4 additions & 1 deletion src/init_custom.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class LuckyCli::InitCustom < LuckyCli::Init
private def generate_project(project_name)
api_only = false
authentication = true
with_sec_test = false
project_directory = "."
OptionParser.parse do |parser|
parser.banner = <<-TEXT
Expand All @@ -21,11 +22,12 @@ class LuckyCli::InitCustom < LuckyCli::Init
Examples:

lucky init.custom my_project
lucky init.custom my_api --api --no-auth --dir ~/Projects/
lucky init.custom my_api --api --no-auth --with-sec-test --dir ~/Projects/

TEXT
parser.on("--api", "Generates an api-only web app") { api_only = true }
parser.on("--no-auth", "Skips generating authentication") { authentication = false }
parser.on("--with-sec-test", "Adds in SecTest integration") { with_sec_test = true }
parser.on("--dir DIRECTORY", "Specify the directory to generate your app in. Default is current directory") { |dir|
project_directory = dir
}
Expand All @@ -39,6 +41,7 @@ class LuckyCli::InitCustom < LuckyCli::Init
project_name: project_name.to_s,
api_only: api_only,
generate_auth: authentication,
with_sec_tester: with_sec_test,
project_directory: project_directory
)
end
Expand Down
8 changes: 8 additions & 0 deletions src/lucky_cli/app_with_sec_tester_template.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class AppWithSecTesterTemplate < Teeplate::FileTree
directory "#{__DIR__}/../app_with_sec_tester"

getter? generate_auth, browser

def initialize(@generate_auth : Bool, @browser : Bool)
end
end
4 changes: 2 additions & 2 deletions src/lucky_cli/src_template.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ require "random/secure"
class SrcTemplate < Teeplate::FileTree
directory "#{__DIR__}/../web_app_skeleton"
getter project_name
getter? api_only, generate_auth
getter? api_only, generate_auth, with_sec_tester
getter crystal_project_name : String

def initialize(@project_name : String, @generate_auth : Bool, @api_only : Bool)
def initialize(@project_name : String, @generate_auth : Bool, @api_only : Bool, @with_sec_tester : Bool)
@crystal_project_name = @project_name.gsub("-", "_")
end

Expand Down
Loading