diff --git a/.circleci/build-and-test/jobs.yml b/.circleci/build-and-test/jobs.yml index 4e32831f8..5e58a99ae 100644 --- a/.circleci/build-and-test/jobs.yml +++ b/.circleci/build-and-test/jobs.yml @@ -4,7 +4,7 @@ steps: - checkout - docker-compose-check - - docker-compose-up-backend + - docker-compose-up-with-elastic-backend - run: name: Run Unit Tests And Create Code Coverage Report command: | @@ -47,7 +47,7 @@ steps: - checkout - docker-compose-check - - docker-compose-up-backend + - docker-compose-up-with-elastic-backend - docker-compose-up-frontend - install-nodejs-machine - disable-npm-audit @@ -61,7 +61,7 @@ wait-for-it --service http://web:8080 --timeout 180 -- echo \"Django is ready\"" - run: name: apply the migrations - command: cd tdrs-backend; docker-compose exec web bash -c "python manage.py makemigrations; python manage.py migrate" + command: cd tdrs-backend; docker-compose exec web bash -c "python manage.py makemigrations; python manage.py migrate" - run: name: Remove existing cypress test users command: cd tdrs-backend; docker-compose exec web python manage.py delete_cypress_users -usernames new-cypress@teamraft.com cypress-admin@teamraft.com diff --git a/.circleci/deployment/commands.yml b/.circleci/deployment/commands.yml index 43adb60e3..347f119b5 100644 --- a/.circleci/deployment/commands.yml +++ b/.circleci/deployment/commands.yml @@ -79,12 +79,24 @@ frontend-appname: default: tdp-frontend type: string + kibana-appname: + default: tdp-kibana + type: string + proxy-appname: + default: tdp-elastic-proxy + type: string cf-space: default: tanf-dev type: string steps: - get-app-deploy-strategy: appname: <> + - run: + name: Install dependencies + command: | + sudo apt update + sudo add-apt-repository ppa:rmescandon/yq + sudo apt-get install yq - run: name: Deploy backend application command: | @@ -92,6 +104,8 @@ $DEPLOY_STRATEGY \ <> \ <> \ + <> \ + <> \ <> deploy-clamav: @@ -115,6 +129,9 @@ frontend-appname: default: tdp-frontend type: string + kibana-appname: + default: tdp-kibana + type: string # So the frontend knows what space its in for the banner. # I am unclear if the domain is a reliable metric to make this function # It seems like it might not be working @@ -136,6 +153,7 @@ $DEPLOY_STRATEGY \ <> \ <> \ + <> \ <> \ <> diff --git a/.circleci/util/commands.yml b/.circleci/util/commands.yml index ebbdfb7e1..09d175b69 100644 --- a/.circleci/util/commands.yml +++ b/.circleci/util/commands.yml @@ -11,6 +11,12 @@ name: Build and spin-up Django API service command: cd tdrs-backend; docker network create external-net; docker-compose up -d --build + docker-compose-up-with-elastic-backend: + steps: + - run: + name: Build and spin-up Django API service + command: cd tdrs-backend; docker network create external-net; docker-compose --profile elastic_setup up -d --build + cf-check: steps: - run: diff --git a/.gitconfig b/.gitconfig index 4c46daac8..2b2b988bc 100644 --- a/.gitconfig +++ b/.gitconfig @@ -7,6 +7,7 @@ allowed = [A-Z]+_KEY=..echo \".{S3_CREDENTIALS}\" [|] jq -r .+ allowed = ./tdrs-backend/.env.example:.* allowed = ./tdrs-backend/docker-compose.yml:57:.* + allowed = ./tdrs-backend/manifest.proxy.yml:* allowed = regexes.json:.* allowed = ./scripts/copy-login-gov-keypair.sh:14:JWT_KEY=.* allowed = scripts/deploy-backend.sh:.+:DJANGO_SECRET_KEY=..python -c .from secrets import token_urlsafe. print.token_urlsafe..* diff --git a/docs/Sprint-Review/sprint-91-summary.md b/docs/Sprint-Review/sprint-91-summary.md new file mode 100644 index 000000000..bfa6372a2 --- /dev/null +++ b/docs/Sprint-Review/sprint-91-summary.md @@ -0,0 +1,62 @@ +# Sprint 91 Summary + +01/17/2024 - 01/30/2024 + +Velocity (Dev): 24 + +## Sprint Goal +* Dev: + * Continue parsing engine development and begin work on enhancement tickets + * #2536 Cat 4 validation + * #1858 Secure OFA staff access to Kibana + * Unblocks #1350 when complete +* DevOps: + * #2790 - Update deployment code to support Kibana and integrate with Standing Elastic instance +* Design: + * Tie up current documentation work + * Continue refinement of research roadmap + + +## Tickets +### Completed/Merged +* [#2751 Resource Card updated with latest coding instructions](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2751) + +### Ready to Merge +* [#2772 Elastic bulk document creation](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2772) +* [#1350 Kibana access from TDP](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/1350) +* [#1858 Spike: Secure Kibana access](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/1858) +* [#2711 Catch report month / year mismatches](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2711) + + + + +### Submitted (QASP Review, OCIO Review) +* [#2790 Kibana Deployment](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2790) +* [#2681 Section 1 Validation clean-up](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2681) + + + +### Closed (not merged) +* N/A + + +--- + +## Moved to Next Sprint (In Progress, Blocked, Raft Review) +### In Progress +* [#2646 - Populate data file summary case aggregates differently per section](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2646) +* [#2820 [bug] Uncaught exception re: parsing error preventing feedback report generation](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2820) +* [#2768 Fix production OWASP scan reporting](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2768) +* [#2799 Generate error mismatching field rpt_month_year w/ header](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2799) +* [#2781 As a developer, I want to have documentation on django migration best practices](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2781) + + +### Blocked +* N/A + +### Raft Review +* [#2536 [spike] Cat 4 validation](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2536) +* [#2592 Deploy celery as a separate cloud.gov app](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2592) +* [#2746 As an STT, I need to know if there are issues with the DOBs reported in my data files](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2746) +* [#2813 Reduce dev environment count](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2813) +* [#2729 As a developer, I want to move migration commands in the pipeline to CircleCI](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/2729) diff --git a/docs/Technical-Documentation/README.md b/docs/Technical-Documentation/README.md index 31d6a3214..e6ef1b203 100644 --- a/docs/Technical-Documentation/README.md +++ b/docs/Technical-Documentation/README.md @@ -19,7 +19,7 @@ This directory contains system and architecture documentation including diagrams - [jwt-key-rotation.md](./jwt-key-rotation.md) : Describes the process for rotating JWT keys in Login.gov. - [nexus-repo.md](./nexus-repo.md) : Setup, connection information, and how to use our Nexus Artifact Repository - [openid-connect.md](./openid-connect.md) : Provides an architecture-level view of the OpenID Connect prototocol. -- [rafts-accessibility-dos-and-donts.md](./rafts-accessibility-dos-and-donts.md) : A succint list of UX guidelines for frontend accessibility. +- [accessibility-guide.md](./accessibility-guide.md) : A guide on getting started with accessibility testing tools and TDP-relevant resources. - [remote-development.md](./remote-development.md) : A guide on doing live remote development in Cloud.gov. - [unit-tests.md](./unit-tests.md) : Outlines our unit testing frameworks and how to run these manually. - [user_role_management.md](./user_role_management.md) : Provides an overview of our user management in Django Administrator Console. diff --git a/docs/Technical-Documentation/accessibility-guide.md b/docs/Technical-Documentation/accessibility-guide.md new file mode 100644 index 000000000..ae938ef1f --- /dev/null +++ b/docs/Technical-Documentation/accessibility-guide.md @@ -0,0 +1,236 @@ +# Accessibility Guide + +**Table of Contents:** +- [Background](#Background) +- [Relevant standards](#Relevant-standards) +- [State of a11y in TDP](#State-of-a11y-in-TDP) +- [What to keep in mind when testing](#What-to-keep-in-mind-when-testing) +- [Testing tools](#Testing-tools) +- [Screen reader use and setup](#Screen-reader-use-and-setup) +- [Do's and don'ts when designing](#Dos-and-donts-when-designing) +- [References](#References) + +--- + +## Background +This document has evolved from its initial state of "Helpful a11y stuff" to one intended to serve more as a guided tour of Raft's accessibility (a11y) practice aimed at enabling more testing and broadly more *consideration* of a11y to be shifted left. + +Additionally, this resource will also aim to document the current state of a11y in TDP to track outstanding enhancements or a11y fixes and to help commentate some issues that may be encountered when testing TDP pages for accessibility conformance. While TDP remains highly accessible as a whole there are certain issues we've identified that demand follow-on work to research or correct. There are also a number of false positives that certain a11y testing tools can identify as problematic. + +--- + +## Relevant standards +There are numerous areas of accessibility law that apply to our work ranging from the ADA (Americans with Disabilities Act) which lays out the broad requirement of accessibility in public spaces and systems to Section 508 of the Rehabilitation Act which mandates a specific standard that Federal systems & resources need to adhere to—specifically WCAG (Web Content Accessibility Standards) 2.0 AA. [See more on US accessibility law](https://www.ada.gov/resources/disability-rights-guide/#top). + +WCAG 2.x standards have four categories with which to evaluate accessibility; Perceivable, Operable, Understandable, and Robust. Within all four categories are three levels of conformance, A, AA, AAA; respectively these correspond to the most barebones standards, good baseline standards, and the most specific standards. + +--- + +## State of a11y in TDP +The [errors audit](https://hackmd.io/79rAOVzISbOvaTNv8nSpeA) tracks all outstanding accessibility issues in the TANF Data Portal, its knowledge center, and Django Admin console. + +--- + +## What to keep in mind when testing +The full picture of what makes for *complete* accessibility testing involves every check in Accessibility Insight's Full Assessment tool, thoughtfulness around what makes for a good experience through the lens of various assistive technologies, and real-world usability testing with people experiencing disabilities. This guide isn't going to be able to deliver all that—but it will seek to lay out some illustrative examples and frequently useful questions to pose of an experience when you're testing it for conformance. + +The following checklist is organized via WCAG 2.x's categories. While a few items explicitly involve screen-readers or other assistive technologies, most items should be able to be checked "yes" regardless of your mode of interaction with the webpage (vision & mouse, keyboard only, screenreader, etc...). + + +### Is it Perceivable? +The user must be able to *perceive* all the information being presented. + +- [ ] If there are non-decorative images on the page, do they have alt-text? + - [ ] Does the alt text convey all the relevant information a sighted user would get from the image? +- [ ] Is there a visible focus indicator for every element of the interface as you tab through it? +- [ ] Do all interface items have sufficient contrast against their backgrounds? + - [ ] If information is communicated by color are there also alternatives beyond color for folks who can't see or distinguish the colors? +- [ ] Do related areas of the interface have visual proximity to each other? +- [ ] Are all interface items read correctly by screenreaders? + +### Is it Operable? +The user must be able to *use* all interactive portions of the experience—and navigate to all areas of it. + +- [ ] When navigating the page with the keyboard can you tab to every interactive element? + - [ ] Is the order in which items are focused logical? + - [ ] If there's a disabled element is it correctly marked up with ARIA and read to screen readers? +- [ ] Is there sufficient (read as: generous) time to read and interact with transient elements of the page (e.g. the timeout modal dialog)? +- [ ] Does the navigable experience "shrink" when pop-over content is open? (e.g. Modal dialogs & the opened side navigation) + - [ ] Is content behind the pop-over content shaded out? + - [ ] Is keyboard focus constrained to the pop-over content alone? + +### Is it Understandable? +The user must be able to understand the information being presented by the experience and *how* to operate it. + +- [ ] Is the language used on the page [plain](https://www.plainlanguage.gov/guidelines/) rather than technical or overcomplicated? + - [ ] Are abbreviations defined alongside their first usage? + - [ ] Are unusual words explained? +- [ ] Do interface elements used for navigation appear and behave consistently throughout the experience? +- [ ] Does the experience make it clear when elements or page contexts change? (e.g. When you navigate to a new page or a new piece of content appears) +- [ ] Are there labels and instructions on the page to help prevent errors? + - [ ] When errors appear is it clear what action or form item they relate to? + - [ ] When errors appear do they suggest something for the user to try next to correct or move past them? + +### Is it Robust? +The experience and the content within it need to be *reliable* and play nicely with a range of assistive technologies. + +- [ ] Does this experience work as intended when navigated via Safari and Voiceover? + - [ ] Does it work as intended when tested in other browsers and via other screen readers? +- [ ] Is the page parsed correctly by testing tools? + + +--- + +## Testing tools + + + + +### In-browser tools + +| Extension | Description | Link | +| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| **Alt-text tester** | Flags images on a page that are missing alt text, provides an easy way to view alt text for any image that has it. | [Chrome](https://chrome.google.com/webstore/detail/alt-text-tester/koldhcllpbdfcdpfpbldbicbgddglodk?hl=en) | +| **Accessibility Insights** | Great for getting familiar with WCAG. It has both fast-pass assessments and a guided way to do manual testing. Plus—when doing manual testing—deep dive explainers on each test (see the info buttons next to the headings of any given test). | [Chrome](https://chrome.google.com/webstore/detail/accessibility-insights-fo/pbjjkligggfmakdaogkfomddhfmpjeni), [Edge](https://microsoftedge.microsoft.com/addons/detail/ghbhpcookfemncgoinjblecnilppimih) | +| **Axe DevTools** | Great fast-pass scanner, will identify best practice issues as well as WCAG compliance violations. | [Chrome](https://chrome.google.com/webstore/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US), [Firefox](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Edge](https://microsoftedge.microsoft.com/addons/detail/axe-devtools-web-access/kcenlimkmjjkdfcaleembgmldmnnlfkn) | +| **Accessible Colors** | Web tool tool that will tell you whether two hex codes have sufficient contrast with each other, show you what they look like, and suggests the closest alternative color if they don't have sufficient contrast. Note that the default values for font size, weight, and compliance can be left alone for most purposes. | [Site](https://accessible-colors.com/) | + + +If you go through all the manual tests in the full Accessibility Insights assessment having these scripts bookmarked will be useful! Running them is as simple as opening the bookmark while viewing the page you're testing. + +**Tests whether page text can be spaced out and comply with requirements without breaking layout** + +```` +javascript:(function(){ var style = document.createElement(%27style%27), styleContent = document.createTextNode(%27* { line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important; } p{ margin-bottom: 2em !important; } %27); style.appendChild(styleContent ); document.getElementsByTagName(%27head%27)[0].appendChild(style); var iframes = document.querySelectorAll(%27iframe%27);for (var i=0; i";break;case Node.COMMENT_NODE:b+="<\!--"+a.nodeValue+"--\>";break;case Node.DOCUMENT_TYPE_NODE:b+="\n"}a=a.nextSibling}return b}(document),d=document.createElement("form");d.method="POST";d.action="https://validator.w3.org/nu/";d.enctype="multipart/form-data";d.target="_blank";d.acceptCharset="utf-8";c("showsource","yes");c("content",e);document.body.appendChild(d);d.submit()})(); +```` + +**Filters results of the above to only WCAG 2.0 violations** + +```` +javascript:(function(){var removeNg=true;var filterStrings=["tag seen","Stray end tag","Bad start tag","violates nesting rules","Duplicate ID","first occurrence of ID","Unclosed element","not allowed as child of element","unclosed elements","not allowed on element","unquoted attribute value","Duplicate attribute"];var filterRE,root,results,result,resultText,i,cnt=0;filterRE=filterStrings.join("|");root=document.getElementById("results");if(!root){alert("No results container found.");return}results=root.getElementsByTagName("li");for(i=0;i> "$BASH_ENV" fi - -exit $ZAP_EXIT diff --git a/tdrs-backend/clamav-router/nginx.conf b/tdrs-backend/clamav-router/nginx.conf index 35e95e7a7..bec070813 100644 --- a/tdrs-backend/clamav-router/nginx.conf +++ b/tdrs-backend/clamav-router/nginx.conf @@ -1,8 +1,9 @@ -events { worker_connections 1024; +events { + worker_connections 1024; } - # This opens a route to clamav prod http{ + resolver {{nameservers}} valid=10s; server { client_max_body_size 100m; listen {{port}}; @@ -21,4 +22,4 @@ http{ proxy_pass_request_headers on; } } -} +} \ No newline at end of file diff --git a/tdrs-backend/docker-compose.yml b/tdrs-backend/docker-compose.yml index a6624688b..e6636e14d 100644 --- a/tdrs-backend/docker-compose.yml +++ b/tdrs-backend/docker-compose.yml @@ -46,15 +46,15 @@ services: - ../scripts/localstack-setup.sh:/docker-entrypoint-initaws.d/localstack-setup.sh kibana: - image: elastic/kibana:7.17.10 + image: docker.elastic.co/kibana/kibana-oss:7.4.2 ports: - 5601:5601 environment: - - xpack.security.encryptionKey="something_at_least_32_characters" - - xpack.security.session.idleTimeout="1h" - - xpack.security.session.lifespan="30d" - volumes: - - ./kibana.yml:/usr/share/kibana/config/kibana.yml + - ELASTICSEARCH_HOSTS="http://elastic:9200" + - SERVER_HOST=kibana + - SERVER_BASEPATH=/kibana + - SERVER_SECURITYRESPONSEHEADERS_REFERRERPOLICY=no-referrer + - CSP_WARNLEGACYBROWSERS=false depends_on: - elastic @@ -101,6 +101,7 @@ services: - CYPRESS_TOKEN - DJANGO_DEBUG - SENDGRID_API_KEY + - BYPASS_KIBANA_AUTH volumes: - .:/tdpapp image: tdp diff --git a/tdrs-backend/elastic_setup/Dockerfile b/tdrs-backend/elastic_setup/Dockerfile new file mode 100644 index 000000000..32e6429f6 --- /dev/null +++ b/tdrs-backend/elastic_setup/Dockerfile @@ -0,0 +1,10 @@ +ARG ELASTIC_VERSION + +FROM docker.elastic.co/elasticsearch/elasticsearch:${ELASTIC_VERSION} + +COPY . / + +RUN ["chmod", "+x", "/entrypoint.sh"] +RUN ["chmod", "+x", "/util.sh"] + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/tdrs-backend/elastic_setup/entrypoint.sh b/tdrs-backend/elastic_setup/entrypoint.sh new file mode 100644 index 000000000..6073b0540 --- /dev/null +++ b/tdrs-backend/elastic_setup/entrypoint.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash + +set -eu +set -o pipefail + +source "${BASH_SOURCE[0]%/*}"/util.sh + + +# -------------------------------------------------------- +# Users declarations + +declare -A users_passwords +users_passwords=( + [kibana_system]="${KIBANA_SYSTEM_PASSWORD:-}" + [ofa_admin]="${OFA_ADMIN_PASSWORD:-}" +) + +declare -A users_roles +users_roles=( + [kibana_system]='kibana_system' + [ofa_admin]='kibana_admin' +) + +# -------------------------------------------------------- +# Roles declarations for custom roles + +declare -A roles_files +roles_files=( + +) + +# -------------------------------------------------------- + + +log 'Waiting for availability of Elasticsearch. This can take several minutes.' + +declare -i exit_code=0 +wait_for_elasticsearch || exit_code=$? + +if ((exit_code)); then + case $exit_code in + 6) + suberr 'Could not resolve host. Is Elasticsearch running?' + ;; + 7) + suberr 'Failed to connect to host. Is Elasticsearch healthy?' + ;; + 28) + suberr 'Timeout connecting to host. Is Elasticsearch healthy?' + ;; + *) + suberr "Connection to Elasticsearch failed. Exit code: ${exit_code}" + ;; + esac + + exit $exit_code +fi + +sublog 'Elasticsearch is running' + +log 'Waiting for initialization of built-in users' + +wait_for_builtin_users || exit_code=$? + +if ((exit_code)); then + suberr 'Timed out waiting for condition' + exit $exit_code +fi + +sublog 'Built-in users were initialized' + +for role in "${!roles_files[@]}"; do + log "Role '$role'" + + declare body_file + body_file="${BASH_SOURCE[0]%/*}/roles/${roles_files[$role]:-}" + if [[ ! -f "${body_file:-}" ]]; then + sublog "No role body found at '${body_file}', skipping" + continue + fi + + sublog 'Creating/updating' + ensure_role "$role" "$(<"${body_file}")" +done + +for user in "${!users_passwords[@]}"; do + log "User '$user'" + if [[ -z "${users_passwords[$user]:-}" ]]; then + sublog 'No password defined, skipping' + continue + fi + + declare -i user_exists=0 + user_exists="$(check_user_exists "$user")" + + if ((user_exists)); then + sublog 'User exists, setting password' + set_user_password "$user" "${users_passwords[$user]}" + else + if [[ -z "${users_roles[$user]:-}" ]]; then + suberr ' No role defined, skipping creation' + continue + fi + + sublog 'User does not exist, creating' + create_user "$user" "${users_passwords[$user]}" "${users_roles[$user]}" + fi +done + +log "Elastic setup completed. Exiting with code: $?" diff --git a/tdrs-backend/elastic_setup/util.sh b/tdrs-backend/elastic_setup/util.sh new file mode 100644 index 000000000..045110249 --- /dev/null +++ b/tdrs-backend/elastic_setup/util.sh @@ -0,0 +1,240 @@ +#!/usr/bin/env bash + +# Log a message. +function log { + echo "[+] $1" +} + +# Log a message at a sub-level. +function sublog { + echo " ⠿ $1" +} + +# Log an error. +function err { + echo "[x] $1" >&2 +} + +# Log an error at a sub-level. +function suberr { + echo " ⠍ $1" >&2 +} + +# Poll the 'elasticsearch' service until it responds with HTTP code 200. +function wait_for_elasticsearch { + local elasticsearch_host="${ELASTICSEARCH_HOST:-elastic}" + + local -a args=( '-s' '-D-' '-m15' '-w' '%{http_code}' "http://${elasticsearch_host}:9200/" ) + + if [[ -n "${ELASTIC_PASSWORD:-}" ]]; then + args+=( '-u' "elastic:${ELASTIC_PASSWORD}" ) + fi + + local -i result=1 + local output + + # retry for max 300s (60*5s) + for _ in $(seq 1 60); do + local -i exit_code=0 + output="$(curl "${args[@]}")" || exit_code=$? + + if ((exit_code)); then + result=$exit_code + fi + + if [[ "${output: -3}" -eq 200 ]]; then + result=0 + break + fi + + sleep 5 + done + + if ((result)) && [[ "${output: -3}" -ne 000 ]]; then + echo -e "\n${output::-3}" + fi + + return $result +} + +# Poll the Elasticsearch users API until it returns users. +function wait_for_builtin_users { + local elasticsearch_host="${ELASTICSEARCH_HOST:-elastic}" + + local -a args=( '-s' '-D-' '-m15' "http://${elasticsearch_host}:9200/_security/user?pretty" ) + + if [[ -n "${ELASTIC_PASSWORD:-}" ]]; then + args+=( '-u' "elastic:${ELASTIC_PASSWORD}" ) + fi + + local -i result=1 + + local line + local -i exit_code + local -i num_users + + # retry for max 30s (30*1s) + for _ in $(seq 1 30); do + num_users=0 + + # read exits with a non-zero code if the last read input doesn't end + # with a newline character. The printf without newline that follows the + # curl command ensures that the final input not only contains curl's + # exit code, but causes read to fail so we can capture the return value. + # Ref. https://unix.stackexchange.com/a/176703/152409 + while IFS= read -r line || ! exit_code="$line"; do + if [[ "$line" =~ _reserved.+true ]]; then + (( num_users++ )) + fi + done < <(curl "${args[@]}"; printf '%s' "$?") + + if ((exit_code)); then + result=$exit_code + fi + + # we expect more than just the 'elastic' user in the result + if (( num_users > 1 )); then + result=0 + break + fi + + sleep 1 + done + + return $result +} + +# Verify that the given Elasticsearch user exists. +function check_user_exists { + local username=$1 + + local elasticsearch_host="${ELASTICSEARCH_HOST:-elastic}" + + local -a args=( '-s' '-D-' '-m15' '-w' '%{http_code}' + "http://${elasticsearch_host}:9200/_security/user/${username}" + ) + + if [[ -n "${ELASTIC_PASSWORD:-}" ]]; then + args+=( '-u' "elastic:${ELASTIC_PASSWORD}" ) + fi + + local -i result=1 + local -i exists=0 + local output + + output="$(curl "${args[@]}")" + if [[ "${output: -3}" -eq 200 || "${output: -3}" -eq 404 ]]; then + result=0 + fi + if [[ "${output: -3}" -eq 200 ]]; then + exists=1 + fi + + if ((result)); then + echo -e "\n${output::-3}" + else + echo "$exists" + fi + + return $result +} + +# Set password of a given Elasticsearch user. +function set_user_password { + local username=$1 + local password=$2 + + local elasticsearch_host="${ELASTICSEARCH_HOST:-elastic}" + + local -a args=( '-s' '-D-' '-m15' '-w' '%{http_code}' + "http://${elasticsearch_host}:9200/_security/user/${username}/_password" + '-X' 'POST' + '-H' 'Content-Type: application/json' + '-d' "{\"password\" : \"${password}\"}" + ) + + if [[ -n "${ELASTIC_PASSWORD:-}" ]]; then + args+=( '-u' "elastic:${ELASTIC_PASSWORD}" ) + fi + + local -i result=1 + local output + + output="$(curl "${args[@]}")" + if [[ "${output: -3}" -eq 200 ]]; then + result=0 + fi + + if ((result)); then + echo -e "\n${output::-3}\n" + fi + + return $result +} + +# Create the given Elasticsearch user. +function create_user { + local username=$1 + local password=$2 + local role=$3 + + local elasticsearch_host="${ELASTICSEARCH_HOST:-elastic}" + + local -a args=( '-s' '-D-' '-m15' '-w' '%{http_code}' + "http://${elasticsearch_host}:9200/_security/user/${username}" + '-X' 'POST' + '-H' 'Content-Type: application/json' + '-d' "{\"password\":\"${password}\",\"roles\":[\"${role}\"]}" + ) + + if [[ -n "${ELASTIC_PASSWORD:-}" ]]; then + args+=( '-u' "elastic:${ELASTIC_PASSWORD}" ) + fi + + local -i result=1 + local output + + output="$(curl "${args[@]}")" + if [[ "${output: -3}" -eq 200 ]]; then + result=0 + fi + + if ((result)); then + echo -e "\n${output::-3}\n" + fi + + return $result +} + +# Ensure that the given Elasticsearch role is up-to-date, create it if required. +function ensure_role { + local name=$1 + local body=$2 + + local elasticsearch_host="${ELASTICSEARCH_HOST:-elastic}" + + local -a args=( '-s' '-D-' '-m15' '-w' '%{http_code}' + "http://${elasticsearch_host}:9200/_security/role/${name}" + '-X' 'POST' + '-H' 'Content-Type: application/json' + '-d' "$body" + ) + + if [[ -n "${ELASTIC_PASSWORD:-}" ]]; then + args+=( '-u' "elastic:${ELASTIC_PASSWORD}" ) + fi + + local -i result=1 + local output + + output="$(curl "${args[@]}")" + if [[ "${output: -3}" -eq 200 ]]; then + result=0 + fi + + if ((result)); then + echo -e "\n${output::-3}\n" + fi + + return $result +} \ No newline at end of file diff --git a/tdrs-backend/kibana.yml b/tdrs-backend/kibana.yml deleted file mode 100644 index dad4335d0..000000000 --- a/tdrs-backend/kibana.yml +++ /dev/null @@ -1,2 +0,0 @@ -elasticsearch.hosts: ["http://elastic:9200"] -server.host: kibana diff --git a/tdrs-backend/manifest.kibana.yml b/tdrs-backend/manifest.kibana.yml new file mode 100644 index 000000000..181b29ec0 --- /dev/null +++ b/tdrs-backend/manifest.kibana.yml @@ -0,0 +1,16 @@ +version: 1 +applications: + - name: tdp-kibana + memory: 2G + disk_quota: 2G + instances: 1 + env: + CGAPPNAME_PROXY: {{ proxy_hostname }} + SERVER_BASEPATH: /kibana + SERVER_SECURITYRESPONSEHEADERS_REFERRERPOLICY: no-referrer + CSP_WARNLEGACYBROWSERS: false + docker: + image: docker.elastic.co/kibana/kibana-oss:7.4.2 + command: | + export ELASTICSEARCH_HOSTS=http://$CGAPPNAME_PROXY.apps.internal:8080 && + /usr/local/bin/dumb-init -- /usr/local/bin/kibana-docker diff --git a/tdrs-backend/manifest.proxy.yml b/tdrs-backend/manifest.proxy.yml new file mode 100644 index 000000000..7ef739c4f --- /dev/null +++ b/tdrs-backend/manifest.proxy.yml @@ -0,0 +1,15 @@ +version: 1 +applications: +- name: tdp-elastic-proxy + memory: 64M + disk_quota: 64M + instances: 1 + services: + - {{ service_0 }} + docker: + image: elipe17/aws-es-proxy:latest + command: | + export ENDPOINT=$(echo $VCAP_SERVICES | grep -Eo 'host[^,]*' | grep -Eo '[^:]*$' | tr -d '"' | sed -e 's/^/https:\/\//') && + export AWS_ACCESS_KEY_ID=$(echo $VCAP_SERVICES | grep -Eo 'access_key[^,]*' | grep -Eo '[^:]*$' | tr -d '"') && + export AWS_SECRET_ACCESS_KEY=$(echo $VCAP_SERVICES | grep -Eo 'secret_key[^,]*' | grep -Eo '[^:]*$' | tr -d '"') && + /usr/local/bin/aws-es-proxy -endpoint $ENDPOINT -listen 0.0.0.0:8080 -verbose -debug diff --git a/tdrs-backend/tdpservice/search_indexes/documents/document_base.py b/tdrs-backend/tdpservice/search_indexes/documents/document_base.py index ea377b283..67903e7e2 100644 --- a/tdrs-backend/tdpservice/search_indexes/documents/document_base.py +++ b/tdrs-backend/tdpservice/search_indexes/documents/document_base.py @@ -13,6 +13,11 @@ class DocumentBase(Document): 'version': fields.IntegerField(), 'quarter': fields.TextField(), 'year': fields.IntegerField(), + 'stt': fields.ObjectField(properties={ + 'name': fields.TextField(), + 'type': fields.TextField(), + 'stt_code': fields.TextField() + }) }) def get_instances_from_related(self, related_instance): diff --git a/tdrs-backend/tdpservice/settings/common.py b/tdrs-backend/tdpservice/settings/common.py index dc4e4c51e..7d9126716 100644 --- a/tdrs-backend/tdpservice/settings/common.py +++ b/tdrs-backend/tdpservice/settings/common.py @@ -340,10 +340,19 @@ class Common(Configuration): # The number of seconds to wait for socket response from clamav-rest AV_SCAN_TIMEOUT = int(os.getenv('AV_SCAN_TIMEOUT', 30)) + # Elastic/Kibana + ELASTICSEARCH_DSL = { + 'default': { + 'hosts': os.getenv('ELASTIC_HOST', 'elastic:9200'), + }, + } + KIBANA_BASE_URL = os.getenv('KIBANA_BASE_URL', 'http://kibana:5601') + BYPASS_KIBANA_AUTH = os.getenv("BYPASS_KIBANA_AUTH", False) + s3_src = "s3-us-gov-west-1.amazonaws.com" CSP_DEFAULT_SRC = ("'none'") - CSP_SCRIPT_SRC = ("'self'", s3_src) + CSP_SCRIPT_SRC = ("'self'", "'unsafe-inline'", s3_src, KIBANA_BASE_URL) CSP_IMG_SRC = ("'self'", "data:", s3_src) CSP_FONT_SRC = ("'self'", s3_src) CSP_CONNECT_SRC = ("'self'", "*.cloud.gov") @@ -351,7 +360,7 @@ class Common(Configuration): CSP_OBJECT_SRC = ("'none'") CSP_FRAME_ANCESTORS = ("'none'") CSP_FORM_ACTION = ("'self'") - CSP_STYLE_SRC = ("'self'", s3_src, "'unsafe-inline'") + CSP_STYLE_SRC = ("'self'", "'unsafe-inline'", s3_src, KIBANA_BASE_URL) #################################### @@ -465,11 +474,4 @@ class Common(Configuration): } } - # Elastic - ELASTICSEARCH_DSL = { - 'default': { - 'hosts': os.getenv('ELASTIC_HOST', 'elastic:9200'), - }, - } - CYPRESS_TOKEN = os.getenv('CYPRESS_TOKEN', None) diff --git a/tdrs-backend/tdpservice/urls.py b/tdrs-backend/tdpservice/urls.py index 26858b356..ee4c37701 100755 --- a/tdrs-backend/tdpservice/urls.py +++ b/tdrs-backend/tdpservice/urls.py @@ -11,7 +11,7 @@ from rest_framework.permissions import AllowAny -from .users.api.authorization_check import AuthorizationCheck +from .users.api.authorization_check import AuthorizationCheck, KibanaAuthorizationCheck from .users.api.login import TokenAuthorizationLoginDotGov, TokenAuthorizationAMS from .users.api.login import CypressLoginDotGovAuthenticationOverride from .users.api.login_redirect_oidc import LoginRedirectAMS, LoginRedirectLoginDotGov @@ -52,6 +52,7 @@ urlpatterns = [ path("v1/", include(urlpatterns)), path("admin/", admin.site.urls, name="admin"), + path("kibana_auth_check/", KibanaAuthorizationCheck.as_view(), name="kibana-authorization-check"), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) # TODO: Supply `terms_of_service` argument in OpenAPI Info once implemented diff --git a/tdrs-backend/tdpservice/users/api/authorization_check.py b/tdrs-backend/tdpservice/users/api/authorization_check.py index 3ac867be0..191e80055 100644 --- a/tdrs-backend/tdpservice/users/api/authorization_check.py +++ b/tdrs-backend/tdpservice/users/api/authorization_check.py @@ -4,10 +4,12 @@ from django.contrib.auth import logout from django.middleware import csrf from django.utils import timezone -from rest_framework.permissions import AllowAny +from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from ..serializers import UserProfileSerializer +from django.http import HttpResponse +from django.conf import settings logger = logging.getLogger(__name__) @@ -49,3 +51,23 @@ def get(self, request, *args, **kwargs): else: logger.info("Auth check FAIL for user on %s", timezone.now()) return Response({"authenticated": False}) + +class KibanaAuthorizationCheck(APIView): + """Check if user is authorized to view Kibana.""" + + query_string = False + pattern_name = "kibana-authorization-check" + permission_classes = [IsAuthenticated] + + def get(self, request, *args, **kwargs): + """Handle get request and verify user is authorized to access kibana.""" + user = request.user + + user_in_valid_group = user.is_ofa_sys_admin + + if (user.hhs_id is not None and user_in_valid_group) or settings.BYPASS_KIBANA_AUTH: + logger.debug(f"User: {user} has correct authentication credentials. Allowing access to Kibana.") + return HttpResponse(status=200) + else: + logger.debug(f"User: {user} has incorrect authentication credentials. Not allowing access to Kibana.") + return HttpResponse(status=401) diff --git a/tdrs-backend/tdpservice/users/models.py b/tdrs-backend/tdpservice/users/models.py index d0a9c924d..2dd8dd3c1 100644 --- a/tdrs-backend/tdpservice/users/models.py +++ b/tdrs-backend/tdpservice/users/models.py @@ -180,6 +180,11 @@ def is_ocio_staff(self) -> bool: """Return whether or not the user is in the ACF OCIO Group.""" return self.is_in_group("ACF OCIO") + @property + def is_ofa_sys_admin(self) -> bool: + """Return whether or not the user is in the OFA System Admin Group.""" + return self.is_in_group("OFA System Admin") + @property def is_deactivated(self): """Check if the user's account status has been set to 'Deactivated'.""" diff --git a/tdrs-frontend/.env b/tdrs-frontend/.env index 882a4aafa..11902ac81 100644 --- a/tdrs-frontend/.env +++ b/tdrs-frontend/.env @@ -36,6 +36,9 @@ REACT_APP_DEBOUNCE_TIME=30000 # 60 seconds == 60 * 1000 == 60000 REACT_APP_EVENT_THROTTLE_TIME=60000 +# Enable the Kibana tab for dev purposes. +# REACT_APP_DEV_KIBANA=true + # Setup SCSS: # The following makes it possible to import SASS modules # without relative paths. Removing this will require all @imports in diff --git a/tdrs-frontend/docker-compose.yml b/tdrs-frontend/docker-compose.yml index 23c0a0669..abfb2ba18 100644 --- a/tdrs-frontend/docker-compose.yml +++ b/tdrs-frontend/docker-compose.yml @@ -28,18 +28,19 @@ services: - NGINX_FRONTEND=tdp-frontend - BACK_END=web - LOCAL_DEV=true + - KIBANA=kibana - REACT_APP_DEVAUTH=${REACT_APP_DEVAUTH} command: > /bin/sh -c "echo 'starting nginx' && - envsubst '$${BACK_END}' < /etc/nginx/locations.conf > /etc/nginx/locations_.conf && + envsubst '$${BACK_END} $${KIBANA}' < /etc/nginx/locations.conf > /etc/nginx/locations_.conf && rm /etc/nginx/locations.conf && cp /etc/nginx/locations_.conf /etc/nginx/locations.conf && envsubst ' - $${BACK_END} $${NGINX_FRONTEND} $${LOCAL_DEV} + $${KIBANA} $${BACK_END} $${NGINX_FRONTEND} $${LOCAL_DEV} '< /etc/nginx/default.conf.template - > /etc/nginx/nginx.conf - && nginx -g 'daemon off;'" + > /etc/nginx/nginx.conf && + nginx -g 'daemon off;'" networks: default: diff --git a/tdrs-frontend/manifest.buildpack.yml b/tdrs-frontend/manifest.buildpack.yml index 93956849a..d8a134a87 100755 --- a/tdrs-frontend/manifest.buildpack.yml +++ b/tdrs-frontend/manifest.buildpack.yml @@ -4,7 +4,7 @@ applications: - name: tdp-frontend buildpacks: - https://github.com/cloudfoundry/nginx-buildpack.git#v1.2.6 - memory: 128M + memory: 256M instances: 1 disk_quota: 256M timeout: 180 diff --git a/tdrs-frontend/manifest.yml b/tdrs-frontend/manifest.yml index f42e7d092..d9b8ef423 100755 --- a/tdrs-frontend/manifest.yml +++ b/tdrs-frontend/manifest.yml @@ -2,7 +2,7 @@ version: 1 applications: - name: tdp-frontend - memory: 32M + memory: 256M instances: 1 disk_quota: 256M timeout: 180 diff --git a/tdrs-frontend/nginx/cloud.gov/buildpack.nginx.conf b/tdrs-frontend/nginx/cloud.gov/buildpack.nginx.conf index 4ed6804f9..514010873 100644 --- a/tdrs-frontend/nginx/cloud.gov/buildpack.nginx.conf +++ b/tdrs-frontend/nginx/cloud.gov/buildpack.nginx.conf @@ -34,6 +34,8 @@ http { limit_req_zone $binary_remote_addr zone=limitreqsbyaddr:20m rate=1000r/s; limit_req_status 444; + resolver {{nameservers}} valid=10s; + server { root public; listen {{port}}; @@ -61,9 +63,9 @@ http { set $ALLOWED_ORIGIN {{env "ALLOWED_ORIGIN"}}; set $CSP "default-src 'self';"; - set $CSP "${CSP}script-src 'self';"; - set $CSP "${CSP}script-src-elem 'self';"; - set $CSP "${CSP}script-src-attr 'self' 'unsafe-inline';"; + set $CSP "${CSP}script-src 'self' 'unsafe-eval' 'unsafe-inline' http://{{env "KIBANA_BASE_URL"}}:5601;"; + set $CSP "${CSP}script-src-elem 'self' 'unsafe-inline' http://{{env "KIBANA_BASE_URL"}}:5601;"; + set $CSP "${CSP}script-src-attr 'self' 'unsafe-inline' http://{{env "KIBANA_BASE_URL"}}:5601;"; set $CSP "${CSP}img-src 'self' data:;"; set $CSP "${CSP}font-src 'self';"; set $CSP "${CSP}connect-src 'self' ${CONNECT_SRC};"; @@ -75,9 +77,9 @@ http { set $CSP "${CSP}child-src 'none';"; set $CSP "${CSP}media-src 'none';"; set $CSP "${CSP}prefetch-src 'none';"; - set $CSP "${CSP}style-src 'self' 'unsafe-inline';"; - set $CSP "${CSP}style-src-elem 'self' 'unsafe-inline';"; - set $CSP "${CSP}style-src-attr 'self' 'unsafe-inline';"; + set $CSP "${CSP}style-src 'self' 'unsafe-inline' http://{{env "KIBANA_BASE_URL"}}:5601;"; + set $CSP "${CSP}style-src-elem 'self' 'unsafe-inline' http://{{env "KIBANA_BASE_URL"}}:5601;"; + set $CSP "${CSP}style-src-attr 'self' 'unsafe-inline' http://{{env "KIBANA_BASE_URL"}}:5601;"; set $CSP "${CSP}worker-src 'none';"; add_header Content-Security-Policy $CSP; diff --git a/tdrs-frontend/nginx/cloud.gov/locations.conf b/tdrs-frontend/nginx/cloud.gov/locations.conf index a6c6d7b42..b7cd5517f 100644 --- a/tdrs-frontend/nginx/cloud.gov/locations.conf +++ b/tdrs-frontend/nginx/cloud.gov/locations.conf @@ -22,8 +22,43 @@ location ~ ^/(v1|admin|static/admin|swagger|redocs) { add_header Access-Control-Allow-Origin 's3-us-gov-west-1.amazonaws.com'; } -if ($request_method ~ ^(TRACE|OPTION)$) { - return 405; +location /kibana/ { + auth_request /kibana_auth_check; + auth_request_set $auth_status $upstream_status; + + proxy_pass http://{{env "KIBANA_BASE_URL"}}:5601/; + proxy_pass_header x-csrftoken; + 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 https; + + proxy_connect_timeout 900; + proxy_read_timeout 300; + proxy_send_timeout 300; + send_timeout 900; + proxy_buffer_size 4k; + + proxy_hide_header Referrer-Policy; +} + +location = /kibana_auth_check { + internal; + proxy_pass http://{{env "BACKEND_HOST"}}.apps.internal:8080/kibana_auth_check/; + proxy_pass_header x-csrftoken; + 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 https; + + proxy_connect_timeout 900; + proxy_read_timeout 300; + proxy_send_timeout 300; + send_timeout 900; +} + +if ($request_method ~ ^(TRACE|OPTION)$) { + return 405; } location = /profile { diff --git a/tdrs-frontend/nginx/local/default.conf.template b/tdrs-frontend/nginx/local/default.conf.template index c4d306340..8cf7b79ab 100644 --- a/tdrs-frontend/nginx/local/default.conf.template +++ b/tdrs-frontend/nginx/local/default.conf.template @@ -68,8 +68,8 @@ http { # CSP header options. All options are set either to none or self except set $CSP "default-src 'none';"; - set $CSP "${CSP}script-src 'self';"; - set $CSP "${CSP}style-src 'self' 'unsafe-inline' http://localhost:3000;"; + set $CSP "${CSP}script-src 'self' 'unsafe-eval' 'unsafe-inline' http://${KIBANA}:5601;"; + set $CSP "${CSP}style-src 'self' 'unsafe-inline' http://localhost:3000 http://${KIBANA}:5601;"; set $CSP "${CSP}img-src 'self' data:;"; set $CSP "${CSP}font-src 'self';"; set $CSP "${CSP}connect-src 'self' localhost:*;"; @@ -79,12 +79,11 @@ http { set $CSP "${CSP}frame-ancestors 'none';"; set $CSP "${CSP}child-src 'none';"; set $CSP "${CSP}media-src 'none';"; - set $CSP "${CSP}prefetch-src 'none';"; set $CSP "${CSP}form-action *;"; - set $CSP "${CSP}script-src-elem 'self' http://localhost:* http://www.w3.org;"; - set $CSP "${CSP}script-src-attr 'self' 'unsafe-inline';"; - set $CSP "${CSP}style-src-elem 'self' 'unsafe-inline';"; - set $CSP "${CSP}style-src-attr 'self';"; + set $CSP "${CSP}script-src-elem 'self' 'unsafe-inline' http://localhost:* http://www.w3.org http://${KIBANA}:5601;"; + set $CSP "${CSP}script-src-attr 'self' 'unsafe-inline' http://${KIBANA}:5601;"; + set $CSP "${CSP}style-src-elem 'self' 'unsafe-inline' http://${KIBANA}:5601;"; + set $CSP "${CSP}style-src-attr 'self' 'unsafe-inline' http://${KIBANA}:5601;"; set $CSP "${CSP}worker-src 'none';"; add_header Content-Security-Policy $CSP; @@ -136,7 +135,6 @@ http { set $CSP "${CSP}frame-src 'None';"; set $CSP "${CSP}child-src 'none';"; set $CSP "${CSP}media-src 'none';"; - set $CSP "${CSP}prefetch-src 'none';"; set $CSP "${CSP}style-src 'self' 'unsafe-inline';"; set $CSP "${CSP}style-src-elem 'self' 'unsafe-inline';"; set $CSP "${CSP}style-src-attr 'self' 'unsafe-inline';"; diff --git a/tdrs-frontend/nginx/local/locations.conf b/tdrs-frontend/nginx/local/locations.conf index 2fc38d3ad..e7ff75fcb 100644 --- a/tdrs-frontend/nginx/local/locations.conf +++ b/tdrs-frontend/nginx/local/locations.conf @@ -21,6 +21,43 @@ location ~ ^/(v1|admin|static/admin|swagger|redocs) { proxy_pass_header x-csrftoken; } +location /kibana/ { + auth_request /kibana_auth_check; + auth_request_set $auth_status $upstream_status; + + proxy_pass http://${KIBANA}:5601/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + + proxy_connect_timeout 300; + proxy_read_timeout 300; + proxy_send_timeout 300; + send_timeout 900; + proxy_buffer_size 4k; + + proxy_hide_header Referrer-Policy; +} + +location = /kibana_auth_check { + internal; + proxy_pass http://${BACK_END}:8080/kibana_auth_check/; + proxy_set_header Host $host:3000; + 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 https; + proxy_set_header Content-Length ""; + proxy_set_header X-Original-URI $request_uri; + + proxy_connect_timeout 300; + proxy_read_timeout 300; + proxy_send_timeout 300; + send_timeout 900; + proxy_pass_header x-csrftoken; +} + location = /profile { index index.html index.htm; try_files $uri $uri/ /index.html; diff --git a/tdrs-frontend/src/components/Header/Header.jsx b/tdrs-frontend/src/components/Header/Header.jsx index 2f6c5335b..201cd55bf 100644 --- a/tdrs-frontend/src/components/Header/Header.jsx +++ b/tdrs-frontend/src/components/Header/Header.jsx @@ -7,6 +7,7 @@ import { accountStatusIsApproved, accountIsInReview, accountCanViewAdmin, + accountCanViewKibana, } from '../../selectors/auth' import NavItem from '../NavItem/NavItem' @@ -29,6 +30,7 @@ function Header() { const userAccessRequestPending = useSelector(accountIsInReview) const userAccessRequestApproved = useSelector(accountStatusIsApproved) const userIsAdmin = useSelector(accountCanViewAdmin) + const userIsSysAdmin = useSelector(accountCanViewKibana) const menuRef = useRef() @@ -137,6 +139,13 @@ function Header() { href={`${process.env.REACT_APP_BACKEND_HOST}/admin/`} /> )} + {userIsSysAdmin && ( + + )} )} diff --git a/tdrs-frontend/src/components/Home/Home.jsx b/tdrs-frontend/src/components/Home/Home.jsx index e102f8acc..8d6b57e36 100644 --- a/tdrs-frontend/src/components/Home/Home.jsx +++ b/tdrs-frontend/src/components/Home/Home.jsx @@ -23,7 +23,6 @@ function Home() { const errorRef = useRef(null) const user = useSelector((state) => state.auth.user) - const role = useSelector(selectPrimaryUserRole) const [errors, setErrors] = useState({}) const [profileInfo, setProfileInfo] = useState({ diff --git a/tdrs-frontend/src/components/STTComboBox/STTComboBox.jsx b/tdrs-frontend/src/components/STTComboBox/STTComboBox.jsx index 4f8adf0f2..21da77488 100644 --- a/tdrs-frontend/src/components/STTComboBox/STTComboBox.jsx +++ b/tdrs-frontend/src/components/STTComboBox/STTComboBox.jsx @@ -3,7 +3,6 @@ import PropTypes from 'prop-types' import { useDispatch, useSelector } from 'react-redux' import { fetchSttList } from '../../actions/sttList' import ComboBox from '../ComboBox' -import Button from '../Button' import Modal from '../Modal' /** @@ -38,8 +37,6 @@ function STTComboBox({ selectStt, selectedStt, handleBlur, error }) { } }, [dispatch, sttListRequest.sttList, numTries, reachedMaxTries]) - const modalRef = useRef() - const headerRef = useRef() const onSignOut = () => { window.location.href = `${process.env.REACT_APP_BACKEND_URL}/logout/oidc` } diff --git a/tdrs-frontend/src/components/SiteMap/SiteMap.jsx b/tdrs-frontend/src/components/SiteMap/SiteMap.jsx index 1df805e7d..5ad40fc4e 100644 --- a/tdrs-frontend/src/components/SiteMap/SiteMap.jsx +++ b/tdrs-frontend/src/components/SiteMap/SiteMap.jsx @@ -3,11 +3,13 @@ import { useSelector } from 'react-redux' import { accountStatusIsApproved, accountCanViewAdmin, + accountCanViewKibana, } from '../../selectors/auth' const SiteMap = ({ user }) => { const userIsApproved = useSelector(accountStatusIsApproved) const userIsAdmin = useSelector(accountCanViewAdmin) + const userIsSysAdmin = useSelector(accountCanViewKibana) return (
@@ -31,6 +33,13 @@ const SiteMap = ({ user }) => { link={`${process.env.REACT_APP_BACKEND_HOST}/admin/`} /> )} + + {userIsSysAdmin && ( + + )}
) } diff --git a/tdrs-frontend/src/selectors/auth.js b/tdrs-frontend/src/selectors/auth.js index b79d2b6b1..eac564a09 100644 --- a/tdrs-frontend/src/selectors/auth.js +++ b/tdrs-frontend/src/selectors/auth.js @@ -59,3 +59,9 @@ export const accountCanViewAdmin = (state) => ['Developer', 'OFA System Admin', 'ACF OCIO', 'OFA Admin'].includes( selectPrimaryUserRole(state)?.name ) + +export const accountCanViewKibana = (state) => + accountStatusIsApproved(state) && + (selectUser(state)?.email?.includes('@acf.hhs.gov') || + process.env.REACT_APP_DEV_KIBANA) && + ['OFA System Admin'].includes(selectPrimaryUserRole(state)?.name)