hq/app/notifications/AnghammaradNotifications.scala (95 lines of code) (raw):

package notifications import com.gu.anghammarad.Anghammarad import com.gu.anghammarad.models.{Email, Notification, Preferred, Target, AwsAccount => Account} import config.Config.{daysBetweenFinalNotificationAndRemediation, daysBetweenWarningAndFinalNotification} import logic.DateUtils.printDay import model.{AwsAccount, HumanUser, IAMUser, Tag} import org.joda.time.DateTime import play.api.Logging import utils.attempt.{Attempt, Failure} import software.amazon.awssdk.services.sns.SnsAsyncClient import scala.concurrent.ExecutionContext import scala.util.control.NonFatal object AnghammaradNotifications extends Logging { def send( notification: Notification, topicArn: String, snsClient: SnsAsyncClient, )(implicit executionContext: ExecutionContext): Attempt[String] = { Attempt.fromFuture(Anghammarad.notify(notification, topicArn, snsClient)) { case NonFatal(e) => Failure( s"Failed to send Anghammarad notification ${e.getMessage}", "Unable to send developer notification", 500, throwable = Some(e) ).attempt }.tap { case Left(failure) => logger.error(failure.logMessage, failure.firstException.orNull) case Right(id) => logger.info(s"Sent notification to ${notification.target}: $id") } } val channel = Preferred(Email) val sourceSystem = "Security HQ Credentials Notifier" private def notificationTargets(awsAccount: AwsAccount, iamUser: IAMUser): List[Target] = Tag.tagsToAnghammaradTargets(iamUser.tags) :+ Account(awsAccount.accountNumber) def outdatedCredentialWarning(awsAccount: AwsAccount, iamUser: IAMUser, problemCreationDate: DateTime, now: DateTime): Notification = { val deadline = printDay(now.plusDays(daysBetweenWarningAndFinalNotification + daysBetweenFinalNotificationAndRemediation)) val message = s""" |Please check the permanent credential ${iamUser.username} in AWS Account ${awsAccount.name}, |which has been flagged because it was last rotated on ${printDay(problemCreationDate)}. |(if you're already planning on doing this, please ignore this message). |If this is not rectified before $deadline, |Security HQ will automatically disable this user at the next opportunity. |""".stripMargin val subject = s"Action required by $deadline: long-lived credential detected in ${awsAccount.name}" Notification(subject, message + genericOutdatedCredentialText, Nil, notificationTargets(awsAccount, iamUser), channel, sourceSystem) } def outdatedCredentialFinalWarning(awsAccount: AwsAccount, iamUser: IAMUser, problemCreationDate: DateTime, now: DateTime): Notification = { val deadline = printDay(now.plusDays(daysBetweenFinalNotificationAndRemediation)) val message = s""" |Please check the permanent credential ${iamUser.username} in AWS Account ${awsAccount.name}, |which has been flagged because it was last rotated on ${printDay(problemCreationDate)}. |(if you're already planning on doing this, please ignore this message). |If this is not rectified before $deadline, |Security HQ will automatically disable this user at the next opportunity. |""".stripMargin val subject = s"Action required by $deadline: long-lived credential in ${awsAccount.name} will be disabled soon" Notification(subject, message + genericOutdatedCredentialText, Nil, notificationTargets(awsAccount, iamUser), channel, sourceSystem) } def outdatedCredentialRemediation(awsAccount: AwsAccount, iamUser: IAMUser, problemCreationDate: DateTime): Notification = { val message = s""" |The permanent credential, ${iamUser.username}, in ${awsAccount.name} was disabled today, |because it was last rotated on ${printDay(problemCreationDate)}. |If you still require the disabled user, add new access keys(s) and rotate regularly. Otherwise, delete them. |""".stripMargin val subject = s"DISABLED long-lived credential in ${awsAccount.name}" Notification(subject, message + genericOutdatedCredentialText, Nil, notificationTargets(awsAccount, iamUser), channel, sourceSystem) } private val genericOutdatedCredentialText = { s""" |To find out how to rectify this, see Grafana (https://metrics.gutools.co.uk/d/bdn97cui5rbi8f/iam-credentials-report?orgId=1). |Here is some helpful documentation on: |rotating credentials: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html, |deleting users: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_manage.html#id_users_deleting_console, |If you have any questions, please contact the Developer Experience team: devx@theguardian.com. |""".stripMargin } def unrecognisedUserRemediation(awsAccount: AwsAccount, iamUser: IAMUser): Notification = { val message = s""" |The permanent credential, ${iamUser.username}, in ${awsAccount.name} was disabled today. |Please check Grafana to review the IAM users in your account (https://metrics.gutools.co.uk/d/bdn97cui5rbi8f/iam-credentials-report?orgId=1). |If you still require the disabled user, ensure they are tagged correctly with their Google username |and have an entry in Janus. |If the disabled user has left the organisation, they should be deleted. |If you have any questions, contact devx@theguardian.com. |""".stripMargin val subject = s"AWS IAM User ${iamUser.username} DISABLED in ${awsAccount.name} Account" Notification(subject, message, Nil, notificationTargets(awsAccount, iamUser), channel, sourceSystem) } }