From 614514d6a2040ed9217f0ddb4f27c3ee50ff0ab2 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Mon, 25 Nov 2019 14:16:15 -0500 Subject: [PATCH 01/19] Update tables to match business logic --- .secrets.baseline | 4 +- ...9_update_schema_based_on_business_logic.py | 198 ++++++++++++++++++ atst/domain/task_orders.py | 6 +- atst/models/application_invitation.py | 5 +- atst/models/application_role.py | 4 +- atst/models/clin.py | 12 +- atst/models/environment_role.py | 4 +- atst/models/mixins/invites.py | 20 +- atst/models/portfolio.py | 6 +- atst/models/portfolio_invitation.py | 2 +- atst/models/portfolio_role.py | 4 +- atst/models/task_order.py | 5 +- atst/models/user.py | 4 +- atst/routes/task_orders/new.py | 6 +- script/seed_sample.py | 21 +- tests/domain/test_portfolios.py | 1 + tests/domain/test_task_orders.py | 2 - tests/domain/test_users.py | 15 +- tests/factories.py | 33 ++- tests/models/test_task_order.py | 12 +- tests/routes/task_orders/test_index.py | 2 +- tests/routes/task_orders/test_new.py | 34 ++- tests/test_access.py | 7 +- 23 files changed, 328 insertions(+), 79 deletions(-) create mode 100644 alembic/versions/67a2151d6269_update_schema_based_on_business_logic.py diff --git a/.secrets.baseline b/.secrets.baseline index 07353d5a..7a81d3cb 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -161,7 +161,7 @@ "hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207", "is_secret": false, "is_verified": false, - "line_number": 32, + "line_number": 31, "type": "Hex High Entropy String" } ], @@ -170,7 +170,7 @@ "hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207", "is_secret": false, "is_verified": false, - "line_number": 656, + "line_number": 657, "type": "Hex High Entropy String" } ] diff --git a/alembic/versions/67a2151d6269_update_schema_based_on_business_logic.py b/alembic/versions/67a2151d6269_update_schema_based_on_business_logic.py new file mode 100644 index 00000000..06fd5d40 --- /dev/null +++ b/alembic/versions/67a2151d6269_update_schema_based_on_business_logic.py @@ -0,0 +1,198 @@ +"""update schema based on business logic + +Revision ID: 67a2151d6269 +Revises: 687fd43489d6 +Create Date: 2019-12-02 14:16:24.902108 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '67a2151d6269' # pragma: allowlist secret +down_revision = '687fd43489d6' # pragma: allowlist secret +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('application_invitations', 'application_role_id', + existing_type=postgresql.UUID(), + nullable=False) + op.alter_column('application_invitations', 'dod_id', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('application_invitations', 'expiration_time', + existing_type=postgresql.TIMESTAMP(timezone=True), + nullable=False) + op.alter_column('application_invitations', 'first_name', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('application_invitations', 'inviter_id', + existing_type=postgresql.UUID(), + nullable=False) + op.alter_column('application_invitations', 'last_name', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('application_invitations', 'token', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('application_roles', 'status', + existing_type=sa.VARCHAR(length=8), + nullable=False) + op.alter_column('clins', 'end_date', + existing_type=sa.DATE(), + nullable=False) + op.alter_column('clins', 'jedi_clin_type', + existing_type=sa.VARCHAR(length=11), + nullable=False) + op.alter_column('clins', 'number', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('clins', 'obligated_amount', + existing_type=sa.NUMERIC(), + nullable=False) + op.alter_column('clins', 'start_date', + existing_type=sa.DATE(), + nullable=False) + op.alter_column('clins', 'total_amount', + existing_type=sa.NUMERIC(), + nullable=False) + op.alter_column('environment_roles', 'status', + existing_type=sa.VARCHAR(length=9), + nullable=False) + op.alter_column('portfolio_invitations', 'dod_id', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('portfolio_invitations', 'expiration_time', + existing_type=postgresql.TIMESTAMP(timezone=True), + nullable=False) + op.alter_column('portfolio_invitations', 'first_name', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('portfolio_invitations', 'inviter_id', + existing_type=postgresql.UUID(), + nullable=False) + op.alter_column('portfolio_invitations', 'last_name', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('portfolio_invitations', 'portfolio_role_id', + existing_type=postgresql.UUID(), + nullable=False) + op.alter_column('portfolio_invitations', 'token', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('portfolio_roles', 'status', + existing_type=sa.VARCHAR(length=8), + nullable=False) + op.alter_column('portfolios', 'defense_component', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('portfolios', 'name', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('task_orders', 'portfolio_id', + existing_type=postgresql.UUID(), + nullable=False) + op.drop_constraint('task_orders_user_id_fkey', 'task_orders', type_='foreignkey') + op.drop_column('task_orders', 'user_id') + op.alter_column('users', 'first_name', + existing_type=sa.VARCHAR(), + nullable=False) + op.alter_column('users', 'last_name', + existing_type=sa.VARCHAR(), + nullable=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('users', 'last_name', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('users', 'first_name', + existing_type=sa.VARCHAR(), + nullable=True) + op.add_column('task_orders', sa.Column('user_id', postgresql.UUID(), autoincrement=False, nullable=True)) + op.create_foreign_key('task_orders_user_id_fkey', 'task_orders', 'users', ['user_id'], ['id']) + op.alter_column('task_orders', 'portfolio_id', + existing_type=postgresql.UUID(), + nullable=True) + op.alter_column('portfolios', 'name', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('portfolios', 'defense_component', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('portfolio_roles', 'status', + existing_type=sa.VARCHAR(length=8), + nullable=True) + op.alter_column('portfolio_invitations', 'token', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('portfolio_invitations', 'portfolio_role_id', + existing_type=postgresql.UUID(), + nullable=True) + op.alter_column('portfolio_invitations', 'last_name', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('portfolio_invitations', 'inviter_id', + existing_type=postgresql.UUID(), + nullable=True) + op.alter_column('portfolio_invitations', 'first_name', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('portfolio_invitations', 'expiration_time', + existing_type=postgresql.TIMESTAMP(timezone=True), + nullable=True) + op.alter_column('portfolio_invitations', 'dod_id', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('environment_roles', 'status', + existing_type=sa.VARCHAR(length=9), + nullable=True) + op.alter_column('clins', 'total_amount', + existing_type=sa.NUMERIC(), + nullable=True) + op.alter_column('clins', 'start_date', + existing_type=sa.DATE(), + nullable=True) + op.alter_column('clins', 'obligated_amount', + existing_type=sa.NUMERIC(), + nullable=True) + op.alter_column('clins', 'number', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('clins', 'jedi_clin_type', + existing_type=sa.VARCHAR(length=11), + nullable=True) + op.alter_column('clins', 'end_date', + existing_type=sa.DATE(), + nullable=True) + op.alter_column('application_roles', 'status', + existing_type=sa.VARCHAR(length=8), + nullable=True) + op.alter_column('application_invitations', 'token', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('application_invitations', 'last_name', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('application_invitations', 'inviter_id', + existing_type=postgresql.UUID(), + nullable=True) + op.alter_column('application_invitations', 'first_name', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('application_invitations', 'expiration_time', + existing_type=postgresql.TIMESTAMP(timezone=True), + nullable=True) + op.alter_column('application_invitations', 'dod_id', + existing_type=sa.VARCHAR(), + nullable=True) + op.alter_column('application_invitations', 'application_role_id', + existing_type=postgresql.UUID(), + nullable=True) + # ### end Alembic commands ### diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 93521843..0c67e1d4 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -11,10 +11,8 @@ class TaskOrders(BaseDomainClass): resource_name = "task_order" @classmethod - def create(cls, creator, portfolio_id, number, clins, pdf): - task_order = TaskOrder( - portfolio_id=portfolio_id, creator=creator, number=number, pdf=pdf - ) + def create(cls, portfolio_id, number, clins, pdf): + task_order = TaskOrder(portfolio_id=portfolio_id, number=number, pdf=pdf) db.session.add(task_order) db.session.commit() diff --git a/atst/models/application_invitation.py b/atst/models/application_invitation.py index d24cc54d..02be5e14 100644 --- a/atst/models/application_invitation.py +++ b/atst/models/application_invitation.py @@ -12,7 +12,10 @@ class ApplicationInvitation( __tablename__ = "application_invitations" application_role_id = Column( - UUID(as_uuid=True), ForeignKey("application_roles.id"), index=True + UUID(as_uuid=True), + ForeignKey("application_roles.id"), + index=True, + nullable=False, ) role = relationship( "ApplicationRole", diff --git a/atst/models/application_role.py b/atst/models/application_role.py index f8f7f201..d65ceac7 100644 --- a/atst/models/application_role.py +++ b/atst/models/application_role.py @@ -46,7 +46,9 @@ class ApplicationRole( UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=True ) - status = Column(SQLAEnum(Status, native_enum=False), default=Status.PENDING) + status = Column( + SQLAEnum(Status, native_enum=False), default=Status.PENDING, nullable=False + ) permission_sets = relationship( "PermissionSet", secondary=application_roles_permission_sets diff --git a/atst/models/clin.py b/atst/models/clin.py index 2802e292..0624d985 100644 --- a/atst/models/clin.py +++ b/atst/models/clin.py @@ -23,12 +23,12 @@ class CLIN(Base, mixins.TimestampsMixin): task_order_id = Column(ForeignKey("task_orders.id"), nullable=False) task_order = relationship("TaskOrder") - number = Column(String, nullable=True) - start_date = Column(Date, nullable=True) - end_date = Column(Date, nullable=True) - total_amount = Column(Numeric(scale=2), nullable=True) - obligated_amount = Column(Numeric(scale=2), nullable=True) - jedi_clin_type = Column(SQLAEnum(JEDICLINType, native_enum=False), nullable=True) + number = Column(String, nullable=False) + start_date = Column(Date, nullable=False) + end_date = Column(Date, nullable=False) + total_amount = Column(Numeric(scale=2), nullable=False) + obligated_amount = Column(Numeric(scale=2), nullable=False) + jedi_clin_type = Column(SQLAEnum(JEDICLINType, native_enum=False), nullable=False) # # NOTE: For now obligated CLINS are CLIN 1 + CLIN 3 diff --git a/atst/models/environment_role.py b/atst/models/environment_role.py index 541b6d40..5b3a2c27 100644 --- a/atst/models/environment_role.py +++ b/atst/models/environment_role.py @@ -43,7 +43,9 @@ class EnvironmentRole( COMPLETED = "completed" DISABLED = "disabled" - status = Column(SQLAEnum(Status, native_enum=False), default=Status.PENDING) + status = Column( + SQLAEnum(Status, native_enum=False), default=Status.PENDING, nullable=False + ) def __repr__(self): return "".format( diff --git a/atst/models/mixins/invites.py b/atst/models/mixins/invites.py index 69e016f8..18916dc4 100644 --- a/atst/models/mixins/invites.py +++ b/atst/models/mixins/invites.py @@ -31,23 +31,29 @@ class InvitesMixin(object): @declared_attr def inviter_id(cls): - return Column(UUID(as_uuid=True), ForeignKey("users.id"), index=True) + return Column( + UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=False + ) @declared_attr def inviter(cls): return relationship("User", foreign_keys=[cls.inviter_id]) - status = Column(SQLAEnum(Status, native_enum=False, default=Status.PENDING)) + status = Column( + SQLAEnum(Status, native_enum=False, default=Status.PENDING, nullable=False) + ) - expiration_time = Column(TIMESTAMP(timezone=True)) + expiration_time = Column(TIMESTAMP(timezone=True), nullable=False) - token = Column(String, index=True, default=lambda: secrets.token_urlsafe()) + token = Column( + String, index=True, default=lambda: secrets.token_urlsafe(), nullable=False + ) email = Column(String, nullable=False) - dod_id = Column(String) - first_name = Column(String) - last_name = Column(String) + dod_id = Column(String, nullable=False) + first_name = Column(String, nullable=False) + last_name = Column(String, nullable=False) phone_number = Column(String) phone_ext = Column(String) diff --git a/atst/models/portfolio.py b/atst/models/portfolio.py index 08a65f1c..ed26b4d4 100644 --- a/atst/models/portfolio.py +++ b/atst/models/portfolio.py @@ -18,8 +18,10 @@ class Portfolio( __tablename__ = "portfolios" id = types.Id() - name = Column(String) - defense_component = Column(String) # Department of Defense Component + name = Column(String, nullable=False) + defense_component = Column( + String, nullable=False + ) # Department of Defense Component app_migration = Column(String) # App Migration complexity = Column(ARRAY(String)) # Application Complexity diff --git a/atst/models/portfolio_invitation.py b/atst/models/portfolio_invitation.py index 55d895c6..4ab9088d 100644 --- a/atst/models/portfolio_invitation.py +++ b/atst/models/portfolio_invitation.py @@ -12,7 +12,7 @@ class PortfolioInvitation( __tablename__ = "portfolio_invitations" portfolio_role_id = Column( - UUID(as_uuid=True), ForeignKey("portfolio_roles.id"), index=True + UUID(as_uuid=True), ForeignKey("portfolio_roles.id"), index=True, nullable=False ) role = relationship( "PortfolioRole", diff --git a/atst/models/portfolio_role.py b/atst/models/portfolio_role.py index 500a0ddd..53204e82 100644 --- a/atst/models/portfolio_role.py +++ b/atst/models/portfolio_role.py @@ -52,7 +52,9 @@ class PortfolioRole( UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=True ) - status = Column(SQLAEnum(Status, native_enum=False), default=Status.PENDING) + status = Column( + SQLAEnum(Status, native_enum=False), default=Status.PENDING, nullable=False + ) permission_sets = relationship( "PermissionSet", secondary=portfolio_roles_permission_sets diff --git a/atst/models/task_order.py b/atst/models/task_order.py index c79d3b83..85bf363a 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -33,12 +33,9 @@ class TaskOrder(Base, mixins.TimestampsMixin): id = types.Id() - portfolio_id = Column(ForeignKey("portfolios.id")) + portfolio_id = Column(ForeignKey("portfolios.id"), nullable=False) portfolio = relationship("Portfolio") - user_id = Column(ForeignKey("users.id")) - creator = relationship("User", foreign_keys="TaskOrder.user_id") - pdf_attachment_id = Column(ForeignKey("attachments.id")) _pdf = relationship("Attachment", foreign_keys=[pdf_attachment_id]) number = Column(String) # Task Order Number diff --git a/atst/models/user.py b/atst/models/user.py index 4ba23895..29b377d6 100644 --- a/atst/models/user.py +++ b/atst/models/user.py @@ -56,8 +56,8 @@ class User( email = Column(String) dod_id = Column(String, unique=True, nullable=False) - first_name = Column(String) - last_name = Column(String) + first_name = Column(String, nullable=False) + last_name = Column(String, nullable=False) phone_number = Column(String) phone_ext = Column(String) service_branch = Column(String) diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 24460204..dca751e3 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -66,7 +66,7 @@ def update_task_order( task_order = TaskOrders.update(task_order_id, **form.data) portfolio_id = task_order.portfolio_id else: - task_order = TaskOrders.create(g.current_user, portfolio_id, **form.data) + task_order = TaskOrders.create(portfolio_id, **form.data) return redirect(url_for(next_page, task_order_id=task_order.id)) else: @@ -181,9 +181,7 @@ def cancel_edit(task_order_id=None, portfolio_id=None): if task_order_id: task_order = TaskOrders.update(task_order_id, **form.data) else: - task_order = TaskOrders.create( - g.current_user, portfolio_id, **form.data - ) + task_order = TaskOrders.create(portfolio_id, **form.data) elif not save and task_order_id: TaskOrders.delete(task_order_id) diff --git a/script/seed_sample.py b/script/seed_sample.py index 11a45530..a9cec40b 100644 --- a/script/seed_sample.py +++ b/script/seed_sample.py @@ -30,6 +30,8 @@ from atst.domain.users import Users from atst.routes.dev import _DEV_USERS as DEV_USERS +from atst.utils import pick + from tests.factories import ( random_service_branch, TaskOrderFactory, @@ -238,6 +240,7 @@ def add_applications_to_portfolio(portfolio): None, first_name=user_data["first_name"], last_name=user_data["last_name"], + email=user_data["email"], ) app_role = ApplicationRoles.create( @@ -263,7 +266,23 @@ def add_applications_to_portfolio(portfolio): def create_demo_portfolio(name, data): try: - portfolio_owner = Users.get_or_create_by_dod_id("2345678901") # Amanda + portfolio_owner = Users.get_or_create_by_dod_id( + "2345678901", + **pick( + [ + "permission_sets", + "first_name", + "last_name", + "email", + "service_branch", + "phone_number", + "citizenship", + "designation", + "date_latest_training", + ], + DEV_USERS["amanda"], + ), + ) # Amanda # auditor = Users.get_by_dod_id("3453453453") # Sally except NotFoundError: print( diff --git a/tests/domain/test_portfolios.py b/tests/domain/test_portfolios.py index 80dade92..41e3fc81 100644 --- a/tests/domain/test_portfolios.py +++ b/tests/domain/test_portfolios.py @@ -9,6 +9,7 @@ from atst.domain.portfolios import ( ) from atst.domain.portfolio_roles import PortfolioRoles from atst.domain.applications import Applications +from atst.domain.application_roles import ApplicationRoles from atst.domain.environments import Environments from atst.domain.permission_sets import PermissionSets, PORTFOLIO_PERMISSION_SETS from atst.models.application_role import Status as ApplicationRoleStatus diff --git a/tests/domain/test_task_orders.py b/tests/domain/test_task_orders.py index 089741d5..8b1eb724 100644 --- a/tests/domain/test_task_orders.py +++ b/tests/domain/test_task_orders.py @@ -96,7 +96,6 @@ def test_create_adds_clins(): }, ] task_order = TaskOrders.create( - creator=portfolio.owner, portfolio_id=portfolio.id, number="0123456789", clins=clins, @@ -127,7 +126,6 @@ def test_update_adds_clins(): }, ] task_order = TaskOrders.create( - creator=task_order.creator, portfolio_id=task_order.portfolio_id, number="0000000000", clins=clins, diff --git a/tests/domain/test_users.py b/tests/domain/test_users.py index b5a24058..20bd8266 100644 --- a/tests/domain/test_users.py +++ b/tests/domain/test_users.py @@ -4,36 +4,41 @@ from uuid import uuid4 from atst.domain.users import Users from atst.domain.exceptions import NotFoundError, AlreadyExistsError, UnauthorizedError +from atst.utils import pick from tests.factories import UserFactory DOD_ID = "my_dod_id" +REQUIRED_KWARGS = {"first_name": "Luke", "last_name": "Skywalker"} def test_create_user(): - user = Users.create(DOD_ID) + user = Users.create(DOD_ID, **REQUIRED_KWARGS) assert user.dod_id == DOD_ID def test_create_user_with_existing_email(): - Users.create(DOD_ID, email="thisusersemail@usersRus.com") + Users.create(DOD_ID, email="thisusersemail@usersRus.com", **REQUIRED_KWARGS) with pytest.raises(AlreadyExistsError): Users.create(DOD_ID, email="thisusersemail@usersRus.com") def test_create_user_with_nonexistent_permission_set(): with pytest.raises(NotFoundError): - Users.create(DOD_ID, permission_sets=["nonexistent"]) + Users.create(DOD_ID, permission_sets=["nonexistent"], **REQUIRED_KWARGS) def test_get_or_create_nonexistent_user(): - user = Users.get_or_create_by_dod_id(DOD_ID) + user = Users.get_or_create_by_dod_id(DOD_ID, **REQUIRED_KWARGS) assert user.dod_id == DOD_ID def test_get_or_create_existing_user(): fact_user = UserFactory.create() - user = Users.get_or_create_by_dod_id(fact_user.dod_id) + user = Users.get_or_create_by_dod_id( + fact_user.dod_id, + **pick(["first_name", "last_name"], fact_user.to_dictionary()), + ) assert user == fact_user diff --git a/tests/factories.py b/tests/factories.py index efb6fb82..9c8f8c02 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -236,9 +236,17 @@ class ApplicationRoleFactory(Base): @classmethod def _create(cls, model_class, *args, **kwargs): with_invite = kwargs.pop("invite", True) - app_role = super()._create(model_class, *args, **kwargs) + app_role = model_class(*args, **kwargs) - if with_invite: + if with_invite and app_role.user: + ApplicationInvitationFactory.create( + role=app_role, + dod_id=app_role.user.dod_id, + first_name=app_role.user.first_name, + last_name=app_role.user.last_name, + email=app_role.user.email, + ) + elif with_invite: ApplicationInvitationFactory.create(role=app_role) return app_role @@ -260,6 +268,14 @@ class PortfolioInvitationFactory(Base): email = factory.Faker("email") status = InvitationStatus.PENDING expiration_time = PortfolioInvitations.current_expiration_time() + dod_id = factory.LazyFunction(random_dod_id) + first_name = factory.Faker("first_name") + last_name = factory.Faker("last_name") + + @classmethod + def _create(cls, model_class, *args, **kwargs): + inviter_id = kwargs.pop("inviter_id", UserFactory.create().id) + return super()._create(model_class, inviter_id=inviter_id, *args, **kwargs) class ApplicationInvitationFactory(Base): @@ -270,6 +286,14 @@ class ApplicationInvitationFactory(Base): status = InvitationStatus.PENDING expiration_time = PortfolioInvitations.current_expiration_time() role = factory.SubFactory(ApplicationRoleFactory, invite=False) + dod_id = factory.LazyFunction(random_dod_id) + first_name = factory.Faker("first_name") + last_name = factory.Faker("last_name") + + @classmethod + def _create(cls, model_class, *args, **kwargs): + inviter_id = kwargs.pop("inviter_id", UserFactory.create().id) + return super()._create(model_class, inviter_id=inviter_id, *args, **kwargs) class AttachmentFactory(Base): @@ -284,11 +308,8 @@ class TaskOrderFactory(Base): class Meta: model = TaskOrder - portfolio = factory.SubFactory( - PortfolioFactory, owner=factory.SelfAttribute("..creator") - ) + portfolio = factory.SubFactory(PortfolioFactory) number = factory.LazyFunction(random_task_order_number) - creator = factory.SubFactory(UserFactory) signed_at = None _pdf = factory.SubFactory(AttachmentFactory) diff --git a/tests/models/test_task_order.py b/tests/models/test_task_order.py index cb1ca7de..91fee15b 100644 --- a/tests/models/test_task_order.py +++ b/tests/models/test_task_order.py @@ -76,7 +76,7 @@ class TestTaskOrderStatus: @patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock) def test_draft_status(self, is_signed, is_completed): # Given that I have a TO that is neither completed nor signed - to = TaskOrder() + to = TaskOrderFactory.create() is_signed.return_value = False is_completed.return_value = False @@ -89,7 +89,7 @@ class TestTaskOrderStatus: def test_active_status(self, is_signed, is_completed, start_date, end_date): # Given that I have a signed TO and today is within its start_date and end_date today = pendulum.today().date() - to = TaskOrder() + to = TaskOrderFactory.create() start_date.return_value = today.subtract(days=1) end_date.return_value = today.add(days=1) @@ -105,7 +105,7 @@ class TestTaskOrderStatus: @patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock) def test_upcoming_status(self, is_signed, is_completed, start_date, end_date): # Given that I have a signed TO and today is before its start_date - to = TaskOrder() + to = TaskOrderFactory.create() start_date.return_value = pendulum.today().add(days=1).date() end_date.return_value = pendulum.today().add(days=2).date() is_signed.return_value = True @@ -120,7 +120,7 @@ class TestTaskOrderStatus: @patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock) def test_expired_status(self, is_signed, is_completed, end_date, start_date): # Given that I have a signed TO and today is after its expiration date - to = TaskOrder() + to = TaskOrderFactory.create() end_date.return_value = pendulum.today().subtract(days=1).date() start_date.return_value = pendulum.today().subtract(days=2).date() is_signed.return_value = True @@ -143,7 +143,7 @@ class TestTaskOrderStatus: class TestBudget: def test_total_contract_amount(self): - to = TaskOrder() + to = TaskOrderFactory.create() assert to.total_contract_amount == 0 clin1 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_1) @@ -156,7 +156,7 @@ class TestBudget: ) def test_total_obligated_funds(self): - to = TaskOrder() + to = TaskOrderFactory.create() assert to.total_obligated_funds == 0 clin1 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_1) diff --git a/tests/routes/task_orders/test_index.py b/tests/routes/task_orders/test_index.py index c800ce25..708b2bdc 100644 --- a/tests/routes/task_orders/test_index.py +++ b/tests/routes/task_orders/test_index.py @@ -30,7 +30,7 @@ def task_order(): portfolio = PortfolioFactory.create(owner=user) attachment = Attachment(filename="sample_attachment", object_name="sample") - return TaskOrderFactory.create(creator=user, portfolio=portfolio) + return TaskOrderFactory.create(portfolio=portfolio) def test_review_task_order_not_draft(client, user_session, task_order): diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index 61a97b82..4d2f5fdb 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -20,14 +20,13 @@ def task_order(): user = UserFactory.create() portfolio = PortfolioFactory.create(owner=user) - return TaskOrderFactory.create(creator=user, portfolio=portfolio) + return TaskOrderFactory.create(portfolio=portfolio) @pytest.fixture def completed_task_order(): portfolio = PortfolioFactory.create() task_order = TaskOrderFactory.create( - creator=portfolio.owner, portfolio=portfolio, create_clins=[{"number": "1234567890123456789012345678901234567890123"}], ) @@ -68,7 +67,7 @@ def test_task_orders_submit_form_step_one_add_pdf(client, user_session, portfoli def test_task_orders_form_step_one_add_pdf_existing_to( client, user_session, task_order ): - user_session(task_order.creator) + user_session(task_order.portfolio.owner) response = client.get( url_for("task_orders.form_step_one_add_pdf", task_order_id=task_order.id) ) @@ -77,7 +76,7 @@ def test_task_orders_form_step_one_add_pdf_existing_to( def test_task_orders_submit_form_step_one_add_pdf_existing_to(client, user_session): task_order = TaskOrderFactory.create() - user_session(task_order.creator) + user_session(task_order.portfolio.owner) response = client.post( url_for( "task_orders.submit_form_step_one_add_pdf", task_order_id=task_order.id @@ -140,7 +139,7 @@ def test_task_orders_submit_form_step_one_validates_object_name( def test_task_orders_form_step_two_add_number(client, user_session, task_order): - user_session(task_order.creator) + user_session(task_order.portfolio.owner) response = client.get( url_for("task_orders.form_step_two_add_number", task_order_id=task_order.id) ) @@ -148,7 +147,7 @@ def test_task_orders_form_step_two_add_number(client, user_session, task_order): def test_task_orders_submit_form_step_two_add_number(client, user_session, task_order): - user_session(task_order.creator) + user_session(task_order.portfolio.owner) form_data = {"number": "1234567890"} response = client.post( url_for( @@ -164,7 +163,7 @@ def test_task_orders_submit_form_step_two_add_number(client, user_session, task_ def test_task_orders_submit_form_step_two_add_number_existing_to( client, user_session, task_order ): - user_session(task_order.creator) + user_session(task_order.portfolio.owner) form_data = {"number": "0000000000"} original_number = task_order.number response = client.post( @@ -179,7 +178,7 @@ def test_task_orders_submit_form_step_two_add_number_existing_to( def test_task_orders_form_step_three_add_clins(client, user_session, task_order): - user_session(task_order.creator) + user_session(task_order.portfolio.owner) response = client.get( url_for("task_orders.form_step_three_add_clins", task_order_id=task_order.id) ) @@ -187,7 +186,7 @@ def test_task_orders_form_step_three_add_clins(client, user_session, task_order) def test_task_orders_submit_form_step_three_add_clins(client, user_session, task_order): - user_session(task_order.creator) + user_session(task_order.portfolio.owner) form_data = { "clins-0-jedi_clin_type": "JEDI_CLIN_1", "clins-0-clin_number": "12312", @@ -237,7 +236,7 @@ def test_task_orders_submit_form_step_three_add_clins_existing_to( TaskOrders.create_clins(task_order.id, clin_list) assert len(task_order.clins) == 2 - user_session(task_order.creator) + user_session(task_order.portfolio.owner) form_data = { "clins-0-jedi_clin_type": "JEDI_CLIN_1", "clins-0-clin_number": "12312", @@ -258,7 +257,7 @@ def test_task_orders_submit_form_step_three_add_clins_existing_to( def test_task_orders_form_step_four_review(client, user_session, completed_task_order): - user_session(completed_task_order.creator) + user_session(completed_task_order.portfolio.owner) response = client.get( url_for( "task_orders.form_step_four_review", task_order_id=completed_task_order.id @@ -270,7 +269,7 @@ def test_task_orders_form_step_four_review(client, user_session, completed_task_ def test_task_orders_form_step_four_review_incomplete_to( client, user_session, task_order ): - user_session(task_order.creator) + user_session(task_order.portfolio.owner) response = client.get( url_for("task_orders.form_step_four_review", task_order_id=task_order.id) ) @@ -280,7 +279,7 @@ def test_task_orders_form_step_four_review_incomplete_to( def test_task_orders_form_step_five_confirm_signature( client, user_session, completed_task_order ): - user_session(completed_task_order.creator) + user_session(completed_task_order.portfolio.owner) response = client.get( url_for( "task_orders.form_step_five_confirm_signature", @@ -293,7 +292,7 @@ def test_task_orders_form_step_five_confirm_signature( def test_task_orders_form_step_five_confirm_signature_incomplete_to( client, user_session, task_order ): - user_session(task_order.creator) + user_session(task_order.portfolio.owner) response = client.get( url_for( "task_orders.form_step_five_confirm_signature", task_order_id=task_order.id @@ -340,9 +339,7 @@ def test_task_orders_submit_task_order(client, user_session, task_order): def test_task_orders_edit_redirects_to_latest_incomplete_step( client, user_session, portfolio, to_factory_args, expected_step ): - task_order = TaskOrderFactory.create( - portfolio=portfolio, creator=portfolio.owner, **to_factory_args - ) + task_order = TaskOrderFactory.create(portfolio=portfolio, **to_factory_args) user_session(portfolio.owner) response = client.get(url_for("task_orders.edit", task_order_id=task_order.id)) @@ -414,8 +411,7 @@ def test_task_orders_update_invalid_data(client, user_session, portfolio): @pytest.mark.skip(reason="Update after implementing errors on TO form") def test_task_order_form_shows_errors(client, user_session, task_order): - creator = task_order.creator - user_session(creator) + user_session(task_order.portfolio.owner) task_order_data = TaskOrderFactory.dictionary() funding_data = slice_data_for_section(task_order_data, "funding") diff --git a/tests/test_access.py b/tests/test_access.py index 8f3d201f..24948ec2 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -487,7 +487,9 @@ def test_portfolios_resend_invitation_access(post_url_assert_status): portfolio = PortfolioFactory.create(owner=owner) prr = PortfolioRoleFactory.create(user=invitee, portfolio=portfolio) - invite = PortfolioInvitationFactory.create(user=UserFactory.create(), role=prr) + invite = PortfolioInvitationFactory.create( + user=UserFactory.create(), role=prr, inviter_id=owner.id + ) url = url_for( "portfolios.resend_invitation", @@ -651,7 +653,6 @@ def test_task_orders_new_get_routes(get_url_assert_status): portfolio = PortfolioFactory.create(owner=owner) task_order = TaskOrderFactory.create( - creator=owner, portfolio=portfolio, create_clins=[{"number": "1234567890123456789012345678901234567890123"}], ) @@ -689,7 +690,7 @@ def test_task_orders_new_post_routes(post_url_assert_status): rando = user_with() portfolio = PortfolioFactory.create(owner=owner) - task_order = TaskOrderFactory.create(portfolio=portfolio, creator=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio) for route, data in post_routes: url = url_for(route, task_order_id=task_order.id) From 57b00715d3c5d2040d44453991143df6a9983cf7 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Mon, 2 Dec 2019 13:53:44 -0500 Subject: [PATCH 02/19] Remove optional validator from CLINField number because it is a required column --- atst/forms/task_order.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index 3ac5640b..2a1b8ad2 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -7,7 +7,7 @@ from wtforms.fields import ( HiddenField, ) from wtforms.fields.html5 import DateField -from wtforms.validators import Required, Optional, Length, NumberRange, ValidationError +from wtforms.validators import Required, Length, NumberRange, ValidationError from flask_wtf import FlaskForm from numbers import Number @@ -61,9 +61,7 @@ class CLINForm(FlaskForm): coerce=coerce_enum, ) - number = StringField( - label=translate("task_orders.form.clin_number_label"), validators=[Optional()] - ) + number = StringField(label=translate("task_orders.form.clin_number_label")) start_date = DateField( translate("task_orders.form.pop_start"), description=translate("task_orders.form.pop_example"), From 26c5b5ea7f3972b51e061466aba0066eefa2cd79 Mon Sep 17 00:00:00 2001 From: dandds Date: Thu, 14 Nov 2019 13:25:30 -0500 Subject: [PATCH 03/19] Add JSON logging back for NGINX container. This configures the NGINX container to log in JSON. It also updates the K8s config so that we mount all of the key/value pairs available in the atst-nginx ConfigMap as files in "/etc/nginx/conf.d" inside the container. This simplifies the config a little. --- deploy/azure/atst-nginx-configmap.yml | 26 ++++++++++++++++++++++++-- deploy/azure/azure.yml | 6 +----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/deploy/azure/atst-nginx-configmap.yml b/deploy/azure/atst-nginx-configmap.yml index e19c6d54..22d6d97e 100644 --- a/deploy/azure/atst-nginx-configmap.yml +++ b/deploy/azure/atst-nginx-configmap.yml @@ -5,8 +5,10 @@ metadata: name: atst-nginx namespace: atat data: - nginx-config: |- + atst.conf: |- server { + access_log /var/log/nginx/access.log json; + listen ${PORT_PREFIX}342; server_name ${MAIN_DOMAIN}; root /usr/share/nginx/html; @@ -18,6 +20,8 @@ data: } } server { + access_log /var/log/nginx/access.log json; + listen ${PORT_PREFIX}343; server_name ${AUTH_DOMAIN}; root /usr/share/nginx/html; @@ -29,6 +33,8 @@ data: } } server { + access_log /var/log/nginx/access.log json; + server_name ${MAIN_DOMAIN}; # access_log /var/log/nginx/access.log json; listen ${PORT_PREFIX}442 ssl; @@ -58,7 +64,8 @@ data: } } server { - # access_log /var/log/nginx/access.log json; + access_log /var/log/nginx/access.log json; + server_name ${AUTH_DOMAIN}; listen ${PORT_PREFIX}443 ssl; listen [::]:${PORT_PREFIX}443 ssl ipv6only=on; @@ -88,3 +95,18 @@ data: uwsgi_param HTTP_X_REQUEST_ID $request_id; } } + 00json_log.conf: |- + log_format json escape=json + '{' + '"timestamp":"$time_iso8601",' + '"msec":"$msec",' + '"request_id":"$request_id",' + '"remote_addr":"$remote_addr",' + '"remote_user":"$remote_user",' + '"request":"$request",' + '"status":$status,' + '"body_bytes_sent":$body_bytes_sent,' + '"referer":"$http_referer",' + '"user_agent":"$http_user_agent",' + '"http_x_forwarded_for":"$http_x_forwarded_for"' + '}'; diff --git a/deploy/azure/azure.yml b/deploy/azure/azure.yml index 8d46fa4b..3ed3ea61 100644 --- a/deploy/azure/azure.yml +++ b/deploy/azure/azure.yml @@ -62,8 +62,7 @@ spec: name: auth volumeMounts: - name: nginx-config - mountPath: "/etc/nginx/conf.d/atst.conf" - subPath: atst.conf + mountPath: "/etc/nginx/conf.d/" - name: uwsgi-socket-dir mountPath: "/var/run/uwsgi" - name: nginx-htpasswd @@ -90,9 +89,6 @@ spec: - name: nginx-config configMap: name: atst-nginx - items: - - key: nginx-config - path: atst.conf - name: uwsgi-socket-dir emptyDir: medium: Memory From a3aa3e69352eadec8d2db976858461843495a4f5 Mon Sep 17 00:00:00 2001 From: dandds Date: Thu, 14 Nov 2019 14:20:17 -0500 Subject: [PATCH 04/19] Config for NGINX SSL/TLS. This adds additional SSL/TLS config to specify the acceptable TLS version, cipher suites, session cache, etc. Values are currently based on the Mozilla Foundation's recommendations for intermediate compatibility: https://wiki.mozilla.org/Security/Server_Side_TLS We will manage NGINX configuration snippets as a K8s ConfigMap so that they can be included in server blocks as-needed. --- deploy/azure/atst-nginx-configmap.yml | 8 +- deploy/azure/azure.yml | 143 ++++++++++++++------------ deploy/azure/kustomization.yaml | 1 + deploy/azure/nginx-snippets.yml | 24 +++++ 4 files changed, 106 insertions(+), 70 deletions(-) create mode 100644 deploy/azure/nginx-snippets.yml diff --git a/deploy/azure/atst-nginx-configmap.yml b/deploy/azure/atst-nginx-configmap.yml index 22d6d97e..b702924c 100644 --- a/deploy/azure/atst-nginx-configmap.yml +++ b/deploy/azure/atst-nginx-configmap.yml @@ -41,6 +41,9 @@ data: listen [::]:${PORT_PREFIX}442 ssl ipv6only=on; ssl_certificate /etc/ssl/private/atat.crt; ssl_certificate_key /etc/ssl/private/atat.key; + # additional SSL/TLS settings + include /etc/nginx/snippets/ssl.conf + location /login-redirect { return 301 https://auth-azure.atat.code.mil$request_uri; } @@ -75,8 +78,9 @@ data: ssl_verify_client on; ssl_verify_depth 10; ssl_client_certificate /etc/ssl/client-ca-bundle.pem; - # Guard against HTTPS -> HTTP downgrade - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; always"; + # additional SSL/TLS settings + include /etc/nginx/snippets/ssl.conf + location / { return 301 https://azure.atat.code.mil$request_uri; } diff --git a/deploy/azure/azure.yml b/deploy/azure/azure.yml index 3ed3ea61..4ed180fb 100644 --- a/deploy/azure/azure.yml +++ b/deploy/azure/azure.yml @@ -30,8 +30,8 @@ spec: - name: atst image: $CONTAINER_IMAGE envFrom: - - configMapRef: - name: atst-envvars + - configMapRef: + name: atst-envvars volumeMounts: - name: atst-config mountPath: "/opt/atat/atst/atst-overrides.ini" @@ -74,14 +74,16 @@ spec: mountPath: "/etc/ssl/" - name: acme mountPath: "/usr/share/nginx/html/.well-known/acme-challenge/" + - name: snippets + mountPath: "/etc/nginx/snippets/" volumes: - name: atst-config secret: secretName: atst-config-ini items: - - key: override.ini - path: atst-overrides.ini - mode: 0644 + - key: override.ini + path: atst-overrides.ini + mode: 0644 - name: nginx-client-ca-bundle configMap: name: nginx-client-ca-bundle @@ -96,19 +98,19 @@ spec: secret: secretName: atst-nginx-htpasswd items: - - key: htpasswd - path: .htpasswd - mode: 0640 + - key: htpasswd + path: .htpasswd + mode: 0640 - name: tls secret: secretName: azure-atat-code-mil-tls items: - - key: tls.crt - path: atat.crt - mode: 0644 - - key: tls.key - path: atat.key - mode: 0640 + - key: tls.crt + path: atat.crt + mode: 0644 + - key: tls.key + path: atat.key + mode: 0640 - name: crls-vol persistentVolumeClaim: claimName: crls-vol-claim @@ -116,9 +118,9 @@ spec: configMap: name: pgsslrootcert items: - - key: cert - path: pgsslrootcert.crt - mode: 0666 + - key: cert + path: pgsslrootcert.crt + mode: 0666 - name: acme configMap: name: acme-challenges @@ -128,9 +130,12 @@ spec: name: uwsgi-config defaultMode: 0666 items: - - key: uwsgi.ini - path: uwsgi.ini - mode: 0644 + - key: uwsgi.ini + path: uwsgi.ini + mode: 0644 + - name: snippets + configMap: + name: nginx-snippets --- apiVersion: extensions/v1beta1 kind: Deployment @@ -157,19 +162,20 @@ spec: containers: - name: atst-worker image: $CONTAINER_IMAGE - args: [ - "/opt/atat/atst/.venv/bin/python", - "/opt/atat/atst/.venv/bin/celery", - "-A", - "celery_worker.celery", - "worker", - "--loglevel=info" - ] + args: + [ + "/opt/atat/atst/.venv/bin/python", + "/opt/atat/atst/.venv/bin/celery", + "-A", + "celery_worker.celery", + "worker", + "--loglevel=info", + ] envFrom: - - configMapRef: - name: atst-envvars - - configMapRef: - name: atst-worker-envvars + - configMapRef: + name: atst-envvars + - configMapRef: + name: atst-worker-envvars volumeMounts: - name: atst-config mountPath: "/opt/atat/atst/atst-overrides.ini" @@ -182,16 +188,16 @@ spec: secret: secretName: atst-config-ini items: - - key: override.ini - path: atst-overrides.ini - mode: 0644 + - key: override.ini + path: atst-overrides.ini + mode: 0644 - name: pgsslrootcert configMap: name: pgsslrootcert items: - - key: cert - path: pgsslrootcert.crt - mode: 0666 + - key: cert + path: pgsslrootcert.crt + mode: 0666 --- apiVersion: extensions/v1beta1 kind: Deployment @@ -218,19 +224,20 @@ spec: containers: - name: atst-beat image: $CONTAINER_IMAGE - args: [ - "/opt/atat/atst/.venv/bin/python", - "/opt/atat/atst/.venv/bin/celery", - "-A", - "celery_worker.celery", - "beat", - "--loglevel=info" - ] + args: + [ + "/opt/atat/atst/.venv/bin/python", + "/opt/atat/atst/.venv/bin/celery", + "-A", + "celery_worker.celery", + "beat", + "--loglevel=info", + ] envFrom: - - configMapRef: - name: atst-envvars - - configMapRef: - name: atst-worker-envvars + - configMapRef: + name: atst-envvars + - configMapRef: + name: atst-worker-envvars volumeMounts: - name: atst-config mountPath: "/opt/atat/atst/atst-overrides.ini" @@ -243,16 +250,16 @@ spec: secret: secretName: atst-config-ini items: - - key: override.ini - path: atst-overrides.ini - mode: 0644 + - key: override.ini + path: atst-overrides.ini + mode: 0644 - name: pgsslrootcert configMap: name: pgsslrootcert items: - - key: cert - path: pgsslrootcert.crt - mode: 0666 + - key: cert + path: pgsslrootcert.crt + mode: 0666 --- apiVersion: v1 kind: Service @@ -264,12 +271,12 @@ metadata: spec: loadBalancerIP: 13.92.235.6 ports: - - port: 80 - targetPort: 8342 - name: http - - port: 443 - targetPort: 8442 - name: https + - port: 80 + targetPort: 8342 + name: http + - port: 443 + targetPort: 8442 + name: https selector: role: web type: LoadBalancer @@ -284,12 +291,12 @@ metadata: spec: loadBalancerIP: 23.100.24.41 ports: - - port: 80 - targetPort: 8343 - name: http - - port: 443 - targetPort: 8443 - name: https + - port: 80 + targetPort: 8343 + name: http + - port: 443 + targetPort: 8443 + name: https selector: role: web type: LoadBalancer diff --git a/deploy/azure/kustomization.yaml b/deploy/azure/kustomization.yaml index 43e6f813..9dee809c 100644 --- a/deploy/azure/kustomization.yaml +++ b/deploy/azure/kustomization.yaml @@ -11,3 +11,4 @@ resources: - nginx-client-ca-bundle.yml - acme-challenges.yml - aadpodidentity.yml + - nginx-snippets.yml diff --git a/deploy/azure/nginx-snippets.yml b/deploy/azure/nginx-snippets.yml new file mode 100644 index 00000000..fc38751f --- /dev/null +++ b/deploy/azure/nginx-snippets.yml @@ -0,0 +1,24 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-snippets + namespace: atat +data: + ssl.conf: |- + # Guard against HTTPS -> HTTP downgrade + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; always"; + # Set SSL protocols, ciphers, and related options + ssl_protocols TLSv1.3 TLSv1.2; + ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384' + ssl_prefer_server_ciphers on; + ssl_ecdh_curve X25519:prime256v1:secp384r1; + ssl_dhparam /etc/ssl/dhparam.pem; + # SSL session options + ssl_session_timeout 4h; + ssl_session_cache shared:SSL:10m; # 1mb = ~4000 sessions + ssl_session_tickets off; + # OCSP Stapling + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4; From 6acc085a771803353af9f75882afd983cd6f178b Mon Sep 17 00:00:00 2001 From: tomdds Date: Mon, 25 Nov 2019 14:03:41 -0500 Subject: [PATCH 05/19] Use dhparam.pem from AZ Key Vault --- deploy/azure/azure.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/deploy/azure/azure.yml b/deploy/azure/azure.yml index 4ed180fb..58491c9c 100644 --- a/deploy/azure/azure.yml +++ b/deploy/azure/azure.yml @@ -23,6 +23,7 @@ spec: labels: app: atst role: web + aadpodidbinding: atat-kv-id-binding spec: securityContext: fsGroup: 101 @@ -76,6 +77,9 @@ spec: mountPath: "/usr/share/nginx/html/.well-known/acme-challenge/" - name: snippets mountPath: "/etc/nginx/snippets/" + - name: nginx-dhparam-secret + mountPath: "/etc/ssl/" + readOnly: true volumes: - name: atst-config secret: @@ -136,6 +140,16 @@ spec: - name: snippets configMap: name: nginx-snippets + - name: nginx-dhparam-secret + flexVolume: + driver: "azure/kv" + options: + usepodidentity: "true" + keyvaultname: "atat-vault-test" + keyvaultobjectnames: "dhparam4096" + keyvaultobjectaliases: "dhparam.pem" + keyvaultobjecttypes: secret + tenantid: "b5ab0e1e-09f8-4258-afb7-fb17654bc5b3" --- apiVersion: extensions/v1beta1 kind: Deployment From 949ffa294dd8f0bba586967548fe03aa1d4279d5 Mon Sep 17 00:00:00 2001 From: tomdds Date: Mon, 25 Nov 2019 14:38:56 -0500 Subject: [PATCH 06/19] Use a single FlexVolume for nginx secrets Just a name update for now, but we'll use the one flex volume to mount all the nginx related secrets going forward. --- deploy/azure/azure.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/azure/azure.yml b/deploy/azure/azure.yml index 58491c9c..0f0033c5 100644 --- a/deploy/azure/azure.yml +++ b/deploy/azure/azure.yml @@ -77,7 +77,7 @@ spec: mountPath: "/usr/share/nginx/html/.well-known/acme-challenge/" - name: snippets mountPath: "/etc/nginx/snippets/" - - name: nginx-dhparam-secret + - name: nginx-secret mountPath: "/etc/ssl/" readOnly: true volumes: @@ -140,7 +140,7 @@ spec: - name: snippets configMap: name: nginx-snippets - - name: nginx-dhparam-secret + - name: nginx-secret flexVolume: driver: "azure/kv" options: From 9469d1ff1b99ba64b89a94d373aa3ad508ec607f Mon Sep 17 00:00:00 2001 From: tomdds Date: Mon, 25 Nov 2019 14:40:10 -0500 Subject: [PATCH 07/19] Introduce TEMPLATE_ID variable for FlexVolume FlexVolume requires you specify the tenant id of the key vault instance, so this will need to be templated in for future enviroments --- deploy/README.md | 1 + deploy/azure/azure.yml | 2 +- script/k8s_config | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/deploy/README.md b/deploy/README.md index be66290d..731d07d7 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -14,6 +14,7 @@ The production configuration (azure.atat.code.mil, currently) is reflected in th - AUTH_DOMAIN: The host domain for the authentication endpoint for the environment. - KV_MI_ID: the fully qualified id (path) of the managed identity for the key vault (instructions on retrieving this are down in section on [Setting up FlexVol](#configuring-the-identity)). Example: /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/RESOURCE_GROUP_NAME/providers/Microsoft.ManagedIdentity/userAssignedIdentities/MANAGED_IDENTITY_NAME - KV_MI_CLIENT_ID: The client id of the managed identity for the key vault. This is a GUID. +- TENANT_ID: The id of the active directory tenant in which the cluster and it's associated users exist. This is a GUID. We use envsubst to substitute values for these variables. There is a wrapper script (script/k8s_config) that will output the compiled configuration, using a combination of kustomize and envsubst. diff --git a/deploy/azure/azure.yml b/deploy/azure/azure.yml index 0f0033c5..ddbbfe18 100644 --- a/deploy/azure/azure.yml +++ b/deploy/azure/azure.yml @@ -149,7 +149,7 @@ spec: keyvaultobjectnames: "dhparam4096" keyvaultobjectaliases: "dhparam.pem" keyvaultobjecttypes: secret - tenantid: "b5ab0e1e-09f8-4258-afb7-fb17654bc5b3" + tenantid: $TENANT_ID --- apiVersion: extensions/v1beta1 kind: Deployment diff --git a/script/k8s_config b/script/k8s_config index ee3c9878..b489c942 100755 --- a/script/k8s_config +++ b/script/k8s_config @@ -13,6 +13,7 @@ SETTINGS=( AUTH_DOMAIN KV_MI_ID KV_MI_CLIENT_ID + TENANT_ID ) # Loop all expected settings. Track ones that are missing and build From 1c4e00e9142468d18391ba11cb4abe3ab7ff8c88 Mon Sep 17 00:00:00 2001 From: tomdds Date: Mon, 25 Nov 2019 15:01:13 -0500 Subject: [PATCH 08/19] Update Deploy Readme for FlexVol consumption Explain via example how you can use FlexVol to mount secrets in our containers. --- deploy/README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/deploy/README.md b/deploy/README.md index 731d07d7..6f8c4cf1 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -218,3 +218,45 @@ Example values: 5. The file `deploy/azure/aadpodidentity.yml` is templated via Kustomize, so you'll need to include clientId (as `KV_MI_CLIENT_ID`) and id (as `KV_MI_ID`) of the managed identity as part of the call to Kustomize. +## Using the FlexVol + +There are 3 steps to using the FlexVol to access secrets from KeyVault + +1. For the resource in which you would like to mount a FlexVol, add a metadata label with the selector from `aadpodidentity.yml` + ``` + metadata: + labels: + app: atst + role: web + aadpodidbinding: atat-kv-id-binding + ``` + +2. Register the FlexVol as a mount and specifiy which secrets you want to mount, along with the file name they should have. The `keyvaultobjectnames`, `keyvaultobjectaliases`, and `keyvaultobjecttypes` correspond to one another, positionally. They are passed as semicolon delimited strings, examples below. + + ``` + - name: volume-of-secrets + flexVolume: + driver: "azure/kv" + options: + usepodidentity: "true" + keyvaultname: "" + keyvaultobjectnames: "mysecret;mykey;mycert" + keyvaultobjectaliases: "mysecret.pem;mykey.txt;mycert.crt" + keyvaultobjecttypes: "secret;key;cert" + tenantid: $TENANT_ID + ``` + +3. Tell the resource where to mount your new volume, using the same name that you specified for the volume above. + ``` + - name: nginx-secret + mountPath: "/usr/secrets/" + readOnly: true + ``` + +4. Once applied, the directory specified in the `mountPath` argument will contain the files you specified in the flexVolume. In our case, you would be able to do this: + ``` + $ kubectl exec -it CONTAINER_NAME -c atst ls /usr/secrets + mycert.crt + mykey.txt + mysecret.pem + ``` From 9b8d5e36626f989c11b468eb38b7f321be1a7923 Mon Sep 17 00:00:00 2001 From: tomdds Date: Tue, 26 Nov 2019 11:03:53 -0500 Subject: [PATCH 09/19] Document generation and updating of dhparams. --- deploy/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deploy/README.md b/deploy/README.md index 6f8c4cf1..25380293 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -170,6 +170,12 @@ Then: kubectl -n atat create secret tls azure-atat-code-mil-tls --key="[path to the private key]" --cert="[path to the full chain]" ``` +### Create the Diffie-Hellman parameters + +Diffie-Hellman parameters allow per-session encryption of SSL traffic to help improve security. We currently store our parameters in KeyVault, the value can be updated using the following command. Note: Generating the new paramter can take over 10 minutes and there won't be any output while it's running. +``` +az keyvault secret set --vault-name --name --value "$(openssl genpkey -genparam -algorithm DH -outform pem -pkeyopt dh_paramgen_prime_len:4096 2> /dev/null)" +``` --- # Setting Up FlexVol for Secrets From 26bb2f46141a5c25a83b0fe92561bc891f4bb4db Mon Sep 17 00:00:00 2001 From: tomdds Date: Tue, 26 Nov 2019 17:01:16 -0500 Subject: [PATCH 10/19] Use mounted all-in-one cert for nginx ssl Mount the combined key and cert for nginx ssl using flexvol and point the necessary nginx config at it. --- deploy/azure/atst-nginx-configmap.yml | 8 ++++---- deploy/azure/azure.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/deploy/azure/atst-nginx-configmap.yml b/deploy/azure/atst-nginx-configmap.yml index b702924c..77de51f9 100644 --- a/deploy/azure/atst-nginx-configmap.yml +++ b/deploy/azure/atst-nginx-configmap.yml @@ -39,8 +39,8 @@ data: # access_log /var/log/nginx/access.log json; listen ${PORT_PREFIX}442 ssl; listen [::]:${PORT_PREFIX}442 ssl ipv6only=on; - ssl_certificate /etc/ssl/private/atat.crt; - ssl_certificate_key /etc/ssl/private/atat.key; + ssl_certificate /etc/ssl/atat.crt; + ssl_certificate_key /etc/ssl/atat.crt; # additional SSL/TLS settings include /etc/nginx/snippets/ssl.conf @@ -72,8 +72,8 @@ data: server_name ${AUTH_DOMAIN}; listen ${PORT_PREFIX}443 ssl; listen [::]:${PORT_PREFIX}443 ssl ipv6only=on; - ssl_certificate /etc/ssl/private/atat.crt; - ssl_certificate_key /etc/ssl/private/atat.key; + ssl_certificate /etc/ssl/atat.crt; + ssl_certificate_key /etc/ssl/atat.crt; # Request and validate client certificate ssl_verify_client on; ssl_verify_depth 10; diff --git a/deploy/azure/azure.yml b/deploy/azure/azure.yml index ddbbfe18..e391fc54 100644 --- a/deploy/azure/azure.yml +++ b/deploy/azure/azure.yml @@ -146,8 +146,8 @@ spec: options: usepodidentity: "true" keyvaultname: "atat-vault-test" - keyvaultobjectnames: "dhparam4096" - keyvaultobjectaliases: "dhparam.pem" + keyvaultobjectnames: "dhparam4096;staging-cert" + keyvaultobjectaliases: "dhparam.pem;atat.crt" keyvaultobjecttypes: secret tenantid: $TENANT_ID --- From 221e9ab26b77c39cb4e03fbf2a9371ca879ad1c1 Mon Sep 17 00:00:00 2001 From: tomdds Date: Tue, 26 Nov 2019 17:02:04 -0500 Subject: [PATCH 11/19] Add a staging overlay for the key vault name Currently we're just using the test vault, but in the future we want to be able to prescribe vault names for different environments via overlay. --- deploy/overlays/staging/kustomization.yaml | 1 + deploy/overlays/staging/vault_name.yml | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 deploy/overlays/staging/vault_name.yml diff --git a/deploy/overlays/staging/kustomization.yaml b/deploy/overlays/staging/kustomization.yaml index 83450cf5..51d34b15 100644 --- a/deploy/overlays/staging/kustomization.yaml +++ b/deploy/overlays/staging/kustomization.yaml @@ -7,6 +7,7 @@ patchesStrategicMerge: - replica_count.yml - ports.yml - envvars.yml + - vault_name.yml patchesJson6902: - target: group: extensions diff --git a/deploy/overlays/staging/vault_name.yml b/deploy/overlays/staging/vault_name.yml new file mode 100644 index 00000000..fa364270 --- /dev/null +++ b/deploy/overlays/staging/vault_name.yml @@ -0,0 +1,12 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: atst +spec: + template: + spec: + volumes: + - name: nginx-secret + flexVolume: + options: + keyvaultname: "atat-vault-test" From 36406372e3d464ccb7acbbd7a2268203e3d701e4 Mon Sep 17 00:00:00 2001 From: tomdds Date: Mon, 2 Dec 2019 11:41:56 -0500 Subject: [PATCH 12/19] Remove unused secret volume for tls key and cert --- deploy/azure/azure.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/deploy/azure/azure.yml b/deploy/azure/azure.yml index e391fc54..99eb847d 100644 --- a/deploy/azure/azure.yml +++ b/deploy/azure/azure.yml @@ -105,16 +105,6 @@ spec: - key: htpasswd path: .htpasswd mode: 0640 - - name: tls - secret: - secretName: azure-atat-code-mil-tls - items: - - key: tls.crt - path: atat.crt - mode: 0644 - - key: tls.key - path: atat.key - mode: 0640 - name: crls-vol persistentVolumeClaim: claimName: crls-vol-claim From 253ddaa49edcebe5ec2d3225ad67643e395418e8 Mon Sep 17 00:00:00 2001 From: tomdds Date: Mon, 2 Dec 2019 11:42:36 -0500 Subject: [PATCH 13/19] Properly register key vault object types --- deploy/azure/azure.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/azure/azure.yml b/deploy/azure/azure.yml index 99eb847d..5859d94a 100644 --- a/deploy/azure/azure.yml +++ b/deploy/azure/azure.yml @@ -138,7 +138,7 @@ spec: keyvaultname: "atat-vault-test" keyvaultobjectnames: "dhparam4096;staging-cert" keyvaultobjectaliases: "dhparam.pem;atat.crt" - keyvaultobjecttypes: secret + keyvaultobjecttypes: "secret;cert" tenantid: $TENANT_ID --- apiVersion: extensions/v1beta1 From 33ce02d0458b090c9cd6f0da7ea8cbec127a47fd Mon Sep 17 00:00:00 2001 From: tomdds Date: Mon, 2 Dec 2019 11:43:26 -0500 Subject: [PATCH 14/19] Better differentiate between master and staging vault config via overlay --- deploy/azure/azure.yml | 2 +- deploy/overlays/staging/{vault_name.yml => flex_vol.yml} | 1 + deploy/overlays/staging/kustomization.yaml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) rename deploy/overlays/staging/{vault_name.yml => flex_vol.yml} (78%) diff --git a/deploy/azure/azure.yml b/deploy/azure/azure.yml index 5859d94a..c8c498c4 100644 --- a/deploy/azure/azure.yml +++ b/deploy/azure/azure.yml @@ -136,7 +136,7 @@ spec: options: usepodidentity: "true" keyvaultname: "atat-vault-test" - keyvaultobjectnames: "dhparam4096;staging-cert" + keyvaultobjectnames: "dhparam4096;master-cert" keyvaultobjectaliases: "dhparam.pem;atat.crt" keyvaultobjecttypes: "secret;cert" tenantid: $TENANT_ID diff --git a/deploy/overlays/staging/vault_name.yml b/deploy/overlays/staging/flex_vol.yml similarity index 78% rename from deploy/overlays/staging/vault_name.yml rename to deploy/overlays/staging/flex_vol.yml index fa364270..ef8ca168 100644 --- a/deploy/overlays/staging/vault_name.yml +++ b/deploy/overlays/staging/flex_vol.yml @@ -10,3 +10,4 @@ spec: flexVolume: options: keyvaultname: "atat-vault-test" + keyvaultobjectnames: "dhparam4096;staging-cert" diff --git a/deploy/overlays/staging/kustomization.yaml b/deploy/overlays/staging/kustomization.yaml index 51d34b15..38251002 100644 --- a/deploy/overlays/staging/kustomization.yaml +++ b/deploy/overlays/staging/kustomization.yaml @@ -7,7 +7,7 @@ patchesStrategicMerge: - replica_count.yml - ports.yml - envvars.yml - - vault_name.yml + - flex_vol.yml patchesJson6902: - target: group: extensions From 5006945cfe73d8880a874b3650dfff018a7d8678 Mon Sep 17 00:00:00 2001 From: tomdds Date: Mon, 2 Dec 2019 14:01:35 -0500 Subject: [PATCH 15/19] Remove tls volumeMount --- deploy/azure/azure.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/deploy/azure/azure.yml b/deploy/azure/azure.yml index c8c498c4..e8a9eacb 100644 --- a/deploy/azure/azure.yml +++ b/deploy/azure/azure.yml @@ -69,8 +69,6 @@ spec: - name: nginx-htpasswd mountPath: "/etc/nginx/.htpasswd" subPath: .htpasswd - - name: tls - mountPath: "/etc/ssl/private" - name: nginx-client-ca-bundle mountPath: "/etc/ssl/" - name: acme From 9ac52493080d84ac7b18261a1305907d08bd95bd Mon Sep 17 00:00:00 2001 From: tomdds Date: Mon, 2 Dec 2019 15:38:15 -0500 Subject: [PATCH 16/19] Add .env files to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 19f4acc5..d8e2290d 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ static/buildinfo.* log/* config/dev.ini +.env* # CRLs /crl From df6ab4a01626af341bf2e7df3ade1d0b39a8528b Mon Sep 17 00:00:00 2001 From: tomdds Date: Mon, 2 Dec 2019 15:40:25 -0500 Subject: [PATCH 17/19] Fix some formatting problems in nginx configs --- deploy/azure/atst-nginx-configmap.yml | 4 ++-- deploy/azure/nginx-snippets.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/azure/atst-nginx-configmap.yml b/deploy/azure/atst-nginx-configmap.yml index 77de51f9..b7959845 100644 --- a/deploy/azure/atst-nginx-configmap.yml +++ b/deploy/azure/atst-nginx-configmap.yml @@ -42,7 +42,7 @@ data: ssl_certificate /etc/ssl/atat.crt; ssl_certificate_key /etc/ssl/atat.crt; # additional SSL/TLS settings - include /etc/nginx/snippets/ssl.conf + include /etc/nginx/snippets/ssl.conf; location /login-redirect { return 301 https://auth-azure.atat.code.mil$request_uri; @@ -79,7 +79,7 @@ data: ssl_verify_depth 10; ssl_client_certificate /etc/ssl/client-ca-bundle.pem; # additional SSL/TLS settings - include /etc/nginx/snippets/ssl.conf + include /etc/nginx/snippets/ssl.conf; location / { return 301 https://azure.atat.code.mil$request_uri; diff --git a/deploy/azure/nginx-snippets.yml b/deploy/azure/nginx-snippets.yml index fc38751f..916d9524 100644 --- a/deploy/azure/nginx-snippets.yml +++ b/deploy/azure/nginx-snippets.yml @@ -10,7 +10,7 @@ data: add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; always"; # Set SSL protocols, ciphers, and related options ssl_protocols TLSv1.3 TLSv1.2; - ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384' + ssl_ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; ssl_ecdh_curve X25519:prime256v1:secp384r1; ssl_dhparam /etc/ssl/dhparam.pem; From 728bb5713f2d1d814f4f1dc5d2ab61b375f51ebb Mon Sep 17 00:00:00 2001 From: tomdds Date: Mon, 2 Dec 2019 15:41:46 -0500 Subject: [PATCH 18/19] Fix flexVol serving of nginx certificates FlexVol requires that you specify certificates as secrets in order to get both the certificate and private key in the appropriate format for nginx to consume. Additionally, flexvol shouldn't interfer with other secrets mounted in it's host directory. --- deploy/azure/atst-nginx-configmap.yml | 4 ++-- deploy/azure/azure.yml | 15 +++++++++------ deploy/overlays/staging/flex_vol.yml | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/deploy/azure/atst-nginx-configmap.yml b/deploy/azure/atst-nginx-configmap.yml index b7959845..5f51c7d6 100644 --- a/deploy/azure/atst-nginx-configmap.yml +++ b/deploy/azure/atst-nginx-configmap.yml @@ -40,7 +40,7 @@ data: listen ${PORT_PREFIX}442 ssl; listen [::]:${PORT_PREFIX}442 ssl ipv6only=on; ssl_certificate /etc/ssl/atat.crt; - ssl_certificate_key /etc/ssl/atat.crt; + ssl_certificate_key /etc/ssl/atat.key; # additional SSL/TLS settings include /etc/nginx/snippets/ssl.conf; @@ -73,7 +73,7 @@ data: listen ${PORT_PREFIX}443 ssl; listen [::]:${PORT_PREFIX}443 ssl ipv6only=on; ssl_certificate /etc/ssl/atat.crt; - ssl_certificate_key /etc/ssl/atat.crt; + ssl_certificate_key /etc/ssl/atat.key; # Request and validate client certificate ssl_verify_client on; ssl_verify_depth 10; diff --git a/deploy/azure/azure.yml b/deploy/azure/azure.yml index e8a9eacb..02952029 100644 --- a/deploy/azure/azure.yml +++ b/deploy/azure/azure.yml @@ -70,14 +70,14 @@ spec: mountPath: "/etc/nginx/.htpasswd" subPath: .htpasswd - name: nginx-client-ca-bundle - mountPath: "/etc/ssl/" + mountPath: "/etc/ssl/client-ca-bundle.pem" + subPath: "client-ca-bundle.pem" - name: acme mountPath: "/usr/share/nginx/html/.well-known/acme-challenge/" - name: snippets mountPath: "/etc/nginx/snippets/" - name: nginx-secret mountPath: "/etc/ssl/" - readOnly: true volumes: - name: atst-config secret: @@ -89,7 +89,10 @@ spec: - name: nginx-client-ca-bundle configMap: name: nginx-client-ca-bundle - defaultMode: 0666 + defaultMode: 0444 + items: + - key: "client-ca-bundle.pem" + path: "client-ca-bundle.pem" - name: nginx-config configMap: name: atst-nginx @@ -134,9 +137,9 @@ spec: options: usepodidentity: "true" keyvaultname: "atat-vault-test" - keyvaultobjectnames: "dhparam4096;master-cert" - keyvaultobjectaliases: "dhparam.pem;atat.crt" - keyvaultobjecttypes: "secret;cert" + keyvaultobjectnames: "dhparam4096;master-cert;master-cert" + keyvaultobjectaliases: "dhparam.pem;atat.key;atat.crt" + keyvaultobjecttypes: "secret;secret;secret" tenantid: $TENANT_ID --- apiVersion: extensions/v1beta1 diff --git a/deploy/overlays/staging/flex_vol.yml b/deploy/overlays/staging/flex_vol.yml index ef8ca168..0ebeea84 100644 --- a/deploy/overlays/staging/flex_vol.yml +++ b/deploy/overlays/staging/flex_vol.yml @@ -10,4 +10,4 @@ spec: flexVolume: options: keyvaultname: "atat-vault-test" - keyvaultobjectnames: "dhparam4096;staging-cert" + keyvaultobjectnames: "dhparam4096;staging-cert;staging-cert" From b1814db50be192c631e433961db2fe26f8a33a28 Mon Sep 17 00:00:00 2001 From: dandds Date: Tue, 3 Dec 2019 09:27:10 -0500 Subject: [PATCH 19/19] Limit integration tests to only run on protected branches. Because of limitations with CircleCI, we should limit Ghost Inspector tests to only run on merge commits to our protected branches. This will allow us to build every commit in CI without exhausting our monthly allotment of Ghost Inspector test runs. Once that setting has been enabled in CircleCI ("build every commit") we will not longer have to worry about what our default branch in the Github repo is. --- .circleci/config.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index af450fad..082ff825 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -293,6 +293,11 @@ workflows: - integration-tests: requires: - docker-build + filters: + branches: + only: + - staging + - master - deploy-staging: requires: - test