Skip to content

Commit a374d42

Browse files
Momchil Zpre-commit-ci[bot]
andauthored
control-service: ability to send authenticated email notifications (#2294)
what: Refactored email notifications package so that authenticated emails can be sent if proper configuration supplied. why: As part of increasing the security of VDK we need to be able to send authenticated notification emails. testing: added unit tests --------- Signed-off-by: mrMoZ1 <mzhivkov@vmware.com> Co-authored-by: github-actions <> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent a778035 commit a374d42

File tree

10 files changed

+354
-58
lines changed

10 files changed

+354
-58
lines changed

projects/control-service/projects/helm_charts/pipelines-control-service/templates/deployment.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,22 @@ spec:
279279
- name: DATAJOBS_DEPLOYMENT_SUPPORTED_PYTHON_VERSIONS
280280
value: {{ .Values.deploymentSupportedPythonVersions | toJson | quote }}
281281
{{- end }}
282+
{{- if .Values.mail.smtp.auth }}
283+
- name: MAIL_SMTP_AUTH
284+
value: "{{ .Values.mail.smtp.auth }}"
285+
- name: MAIL_SMTP_STARTTLS_ENABLE
286+
value: "{{ .Values.mail.smtp.starttls.enable }}"
287+
- name: MAIL_SMTP_USER
288+
value: "{{ .Values.mail.smtp.user }}"
289+
- name: MAIL_SMTP_PASSWORD
290+
value: "{{ .Values.mail.smtp.password }}"
291+
- name: MAIL_SMTP_SSL_PROTOCOLS
292+
value: "{{ .Values.mail.smtp.ssl.protocols }}"
293+
- name: MAIL_SMTP_PORT
294+
value: "{{ .Values.mail.smtp.port }}"
295+
- name: MAIL_TRANSPORT_PROTOCOL
296+
value: "{{ .Values.mail.transport.protocol }}"
297+
{{- end }}
282298

283299

284300
{{- if .Values.extraVars }}

projects/control-service/projects/helm_charts/pipelines-control-service/values.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,3 +1023,19 @@ dataJob:
10231023
# Configures the format of logs output by the control service; if set to `JSON`, logs are formatted to JSON;
10241024
# else logs are formatted to their default user-readable format
10251025
# loggingFormat: TEXT
1026+
1027+
# Configures properties used for sending notifications if emails should go through an
1028+
# authenticated email server. More information:
1029+
# https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html
1030+
mail:
1031+
transport:
1032+
protocol: "smtp"
1033+
smtp:
1034+
auth: false
1035+
starttls:
1036+
enable: false
1037+
user: ""
1038+
password: ""
1039+
ssl:
1040+
protocols: "TLSv1.2"
1041+
port: 587

projects/control-service/projects/pipelines_control_service/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ dependencies { // Implementation dependencies are found on compile classpath of
112112
implementation versions.'com.squareup.okio:okio'
113113
implementation versions.'io.micrometer:micrometer-core'
114114
implementation versions.'io.micrometer:micrometer-registry-prometheus'
115+
testImplementation versions.'com.icegreen.greenmail'
115116
}
116117

117118
task wrapper(type: Wrapper) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2021-2023 VMware, Inc.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.vmware.taurus.service.notification;
7+
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import org.springframework.boot.context.properties.ConfigurationProperties;
11+
import org.springframework.context.annotation.Bean;
12+
import org.springframework.context.annotation.Configuration;
13+
14+
@Configuration
15+
public class EmailConfiguration {
16+
17+
private final String MAIL_SMTP_PASSWORD = "mail.smtp.password";
18+
private final String MAIL_SMTP_USER = "mail.smtp.user";
19+
private final String MAIL_SMTP_AUTH = "mail.smtp.auth";
20+
private final String TRANSPORT_PROTOCOL = "transport.protocol";
21+
22+
@Bean
23+
@ConfigurationProperties(prefix = "mail")
24+
Map<String, String> mail() {
25+
return new HashMap<>();
26+
}
27+
28+
public Map<String, String> smtpWithPrefix() {
29+
Map<String, String> result = new HashMap<>();
30+
var props = this.mail();
31+
props.forEach(
32+
((key, value) -> {
33+
if (key.startsWith("smtp")) {
34+
result.put("mail." + key, value);
35+
}
36+
}));
37+
return result;
38+
}
39+
40+
public String getUsername() {
41+
return smtpWithPrefix().get(MAIL_SMTP_USER);
42+
}
43+
44+
public String getPassword() {
45+
return smtpWithPrefix().get(MAIL_SMTP_PASSWORD);
46+
}
47+
48+
public boolean isAuthEnabled() {
49+
return Boolean.parseBoolean(smtpWithPrefix().get(MAIL_SMTP_AUTH));
50+
}
51+
52+
/**
53+
* Transport protocol to be used to send email messages e.g smtp, pop, imap. Defined in
54+
* application.properties
55+
*
56+
* @return
57+
*/
58+
public String getTransportProtocol() {
59+
return mail().get(TRANSPORT_PROTOCOL);
60+
}
61+
}

projects/control-service/projects/pipelines_control_service/src/main/java/com/vmware/taurus/service/notification/EmailNotification.java

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,17 @@
55

66
package com.vmware.taurus.service.notification;
77

8+
import java.util.Arrays;
9+
import java.util.Properties;
10+
import java.util.stream.Collectors;
811
import javax.mail.Address;
912
import javax.mail.MessagingException;
1013
import javax.mail.SendFailedException;
1114
import javax.mail.Session;
1215
import javax.mail.Transport;
1316
import javax.mail.internet.MimeMessage;
14-
import java.util.Arrays;
15-
import java.util.HashMap;
16-
import java.util.Map;
17-
import java.util.Properties;
18-
import java.util.stream.Collectors;
19-
2017
import org.slf4j.Logger;
2118
import org.slf4j.LoggerFactory;
22-
import org.springframework.boot.context.properties.ConfigurationProperties;
23-
import org.springframework.context.annotation.Bean;
24-
import org.springframework.context.annotation.Configuration;
2519
import org.springframework.stereotype.Component;
2620

2721
/**
@@ -34,54 +28,34 @@
3428
@Component
3529
public class EmailNotification {
3630

37-
@Configuration
38-
public static class SmtpProperties {
39-
40-
@Bean
41-
@ConfigurationProperties(prefix = "mail.smtp")
42-
public Map<String, String> smtp() {
43-
return new HashMap<>();
44-
}
45-
46-
public Map<String, String> smtpWithPrefix() {
47-
Map<String, String> result = new HashMap<>();
48-
var props = this.smtp();
49-
props.forEach(((key, value) -> result.put("mail.smtp." + key, value)));
50-
return result;
51-
}
52-
}
53-
5431
static Logger log = LoggerFactory.getLogger(EmailNotification.class);
5532

5633
private static final String CONTENT_TYPE = "text/html; charset=utf-8";
5734

5835
private final Session session;
36+
private final EmailConfiguration emailConfiguration;
5937

60-
public EmailNotification(SmtpProperties smtpProperties) {
38+
public EmailNotification(EmailConfiguration emailConfiguration) {
39+
this.emailConfiguration = emailConfiguration;
6140
Properties properties = System.getProperties();
62-
properties.putAll(smtpProperties.smtpWithPrefix());
63-
session = Session.getDefaultInstance(properties);
41+
properties.putAll(emailConfiguration.smtpWithPrefix());
42+
properties.setProperty("mail.transport.protocol", emailConfiguration.getTransportProtocol());
43+
session = Session.getInstance(properties);
6444
}
6545

6646
public void send(NotificationContent notificationContent) throws MessagingException {
6747
if (recipientsExist(notificationContent.getRecipients())) {
68-
MimeMessage mimeMessage = new MimeMessage(session);
69-
mimeMessage.setFrom(notificationContent.getSender());
70-
mimeMessage.setRecipients(MimeMessage.RecipientType.TO, notificationContent.getRecipients());
71-
mimeMessage.setSubject(notificationContent.getSubject());
72-
mimeMessage.setContent(notificationContent.getContent(), CONTENT_TYPE);
7348
try {
74-
Transport.send(mimeMessage);
49+
sendEmail(notificationContent, notificationContent.getRecipients());
7550
} catch (SendFailedException firstException) {
7651
log.info(
7752
"Following addresses are invalid: {}",
7853
concatAddresses(firstException.getInvalidAddresses()));
7954
var validUnsentAddresses = firstException.getValidUnsentAddresses();
8055
if (recipientsExist(validUnsentAddresses)) {
8156
log.info("Retrying unsent valid addresses: {}", concatAddresses(validUnsentAddresses));
82-
mimeMessage.setRecipients(MimeMessage.RecipientType.TO, validUnsentAddresses);
8357
try {
84-
Transport.send(mimeMessage);
58+
sendEmail(notificationContent, validUnsentAddresses);
8559
} catch (SendFailedException retriedException) {
8660
log.warn(
8761
"\nwhat: {}\nwhy: {}\nconsequence: {}\ncountermeasure: {}",
@@ -95,18 +69,50 @@ public void send(NotificationContent notificationContent) throws MessagingExcept
9569
}
9670
}
9771

98-
private boolean recipientsExist(Address[] recipients) {
99-
if (recipients.length == 0) {
100-
return false;
72+
private void sendEmail(NotificationContent notificationContent, Address[] recipients)
73+
throws MessagingException {
74+
if (emailConfiguration.isAuthEnabled()) {
75+
sendAuthenticatedEmail(notificationContent, recipients);
76+
} else {
77+
sendUnauthenticatedEmail(notificationContent, recipients);
78+
}
79+
}
80+
81+
private void sendUnauthenticatedEmail(
82+
NotificationContent notificationContent, Address[] recipients) throws MessagingException {
83+
var mimeMessage = prepareMessage(notificationContent);
84+
Transport.send(mimeMessage, recipients);
85+
}
86+
87+
private void sendAuthenticatedEmail(NotificationContent notificationContent, Address[] recipients)
88+
throws MessagingException {
89+
Transport transport = session.getTransport();
90+
var mimeMessage = prepareMessage(notificationContent);
91+
try {
92+
transport.connect(emailConfiguration.getUsername(), emailConfiguration.getPassword());
93+
transport.sendMessage(mimeMessage, recipients);
94+
} finally {
95+
transport.close();
10196
}
102-
return true;
97+
}
98+
99+
private MimeMessage prepareMessage(NotificationContent notificationContent)
100+
throws MessagingException {
101+
MimeMessage mimeMessage = new MimeMessage(session);
102+
mimeMessage.setFrom(notificationContent.getSender());
103+
mimeMessage.setRecipients(MimeMessage.RecipientType.TO, notificationContent.getRecipients());
104+
mimeMessage.setSubject(notificationContent.getSubject());
105+
mimeMessage.setContent(notificationContent.getContent(), CONTENT_TYPE);
106+
return mimeMessage;
107+
}
108+
109+
private boolean recipientsExist(Address[] recipients) {
110+
return recipients.length > 0;
103111
}
104112

105113
String concatAddresses(Address[] addresses) {
106114
return addresses == null
107115
? null
108-
: Arrays.asList(addresses).stream()
109-
.map(addr -> addr.toString())
110-
.collect(Collectors.joining(" "));
116+
: Arrays.stream(addresses).map(Address::toString).collect(Collectors.joining(" "));
111117
}
112118
}

projects/control-service/projects/pipelines_control_service/src/main/resources/application.properties

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,16 @@ datajobs.executions.logsUrl.startTimeOffsetSeconds=${DATAJOBS_EXECUTIONS_LOGS_UR
172172
datajobs.executions.logsUrl.endTimeOffsetSeconds=${DATAJOBS_EXECUTIONS_LOGS_URL_END_TIME_OFFSET_SECONDS:0}
173173

174174
# https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html
175+
mail.transport.protocol= ${MAIL_TRANSPORT_PROTOCOL:smtp}
175176
mail.smtp.host=smtp.vmware.com
177+
# Fill these in if the notification emails need to go through an authenticated email.
178+
mail.smtp.auth=${MAIL_SMTP_AUTH:false}
179+
mail.smtp.starttls.enable=${MAIL_SMTP_STARTTLS_ENABLE:false}
180+
mail.smtp.user=${MAIL_SMTP_USER:}
181+
mail.smtp.password=${MAIL_SMTP_PASSWORD:}
182+
mail.smtp.ssl.protocols= ${MAIL_SMTP_SSL_PROTOCOLS:TLSv1.2}
183+
# Default port is 25 for unathenticated, 587 for authenticated
184+
mail.smtp.port=${MAIL_SMTP_PORT:25}
176185

177186
# Set imagePullPolicy of the job-builder image
178187
# Correspond to those defined by kubernetes

0 commit comments

Comments
 (0)