When i Try send the messege via google and MailKit libriry on a production containerized application I have this exception.
MailKit.Security.SslHandshakeException: An error occurred while attempting to establish an SSL or TLS connection. The host name did not match the name given in the server's SSL certificate.
I use asp.net core 5 and Kestrel. Nginx my reverse proxy.
SSL works fine when I get data using postman. But when I try to send mail, exception occurs.
In development enviroment without nginx server proxy works properly.
This is my nginx.conf file:
server {
client_max_body_size 6M;
listen 80;
server_name myhost.com www.myhost.com;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
client_max_body_size 6M;
listen 443 ssl;
server_name myhost.com www.myhost.com;
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 0;
gzip_types text/plain application/javascript text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype;
#SSL code
ssl_certificate /etc/letsencrypt/live/myhost.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myhost.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
#headers
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 $scheme;
proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
# location /map {
# proxy_pass http://client;
# }
location /admin {
proxy_pass http://client-admin;
}
location /api {
proxy_pass http://api:5000;
}
}
This is my Program.cs file:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace MonumentsMap
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => {
logging.ClearProviders();
logging.AddConsole();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.ConfigureKestrel(conf => {
conf.Limits.MaxRequestBodySize = 6_000_000;
});
});
}
}
This is my Sturtup.cs file
namespace MonumentsMap
{
public class Startup
{
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("WebClientPolicy", builder =>
{
builder.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin();
}));
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
services.AddDbContext<ApplicationContext>(options =>
{
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), o =>
{
o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
});
});
services.AddMemoryCache();
services.AddRepositories();
services.AddServices();
services.AddSingleton(Configuration.GetSection("ImageFilesParams").Get<ImageFilesParams>());
services.AddScoped<CultureCodeResourceFilter>();
services.Configure<MailSettings>(Configuration.GetSection("MailSettings"));
services.AddIdentity<ApplicationUser, IdentityRole>(opts =>
{
opts.Password.RequireDigit = true;
opts.Password.RequireLowercase = true;
opts.Password.RequireUppercase = true;
opts.Password.RequireNonAlphanumeric = false;
opts.Password.RequiredLength = 7;
}).AddEntityFrameworkStores<ApplicationContext>();
services.AddAuthentication(opts =>
{
opts.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Auth:Jwt:Issuer"],
ValidAudience = Configuration["Auth:Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration["Auth:Jwt:Key"])
),
ClockSkew = TimeSpan.Zero,
RequireExpirationTime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateAudience = true
};
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(name: "v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "Monuments Map Api", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
app.UseExceptionHandler("/errors/500");
app.UseStatusCodePagesWithReExecute("/errors/{0}");
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.All
});
app.UseSwagger();
app.UseSwaggerUI(c => {
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Monuments Map Api V1");
});
app.UseCors("WebClientPolicy");
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetService<ApplicationContext>();
var roleManager = serviceScope.ServiceProvider.GetService<RoleManager<IdentityRole>>();
var userManager = serviceScope.ServiceProvider.GetService<UserManager<ApplicationUser>>();
context.Database.Migrate();
var cultures = Configuration.GetSection("SupportedCultures").Get<List<Culture>>();
DbSeed.Seed(context, roleManager, userManager, cultures, Configuration);
}
}
}
}
MailService.cs file
namespace MonumentsMap.Data.Services
{
public class MailService : IMailService
{
private readonly MailSettings _mailSettings;
public MailService(IOptions<MailSettings> mailSettings) => _mailSettings = mailSettings.Value;
public async Task SendEmailAsync(MailRequestDto mailRequest)
{
var email = new MimeMessage();
email.Sender = MailboxAddress.Parse(_mailSettings.Mail);
email.To.Add(MailboxAddress.Parse(mailRequest.ToEmail));
email.Subject = mailRequest.Subject;
var builder = new BodyBuilder();
if (mailRequest.Attachments != null)
{
byte[] fileBytes;
foreach (var file in mailRequest.Attachments)
{
if (file.Length > 0)
{
using (var ms = new MemoryStream())
{
file.CopyTo(ms);
fileBytes = ms.ToArray();
}
builder.Attachments.Add(file.FileName, fileBytes, ContentType.Parse(file.ContentType));
}
}
}
builder.HtmlBody = mailRequest.Body;
email.Body = builder.ToMessageBody();
using var smtp = new SmtpClient();
smtp.Connect(_mailSettings.Host, _mailSettings.Port);
smtp.Authenticate(_mailSettings.Mail, _mailSettings.Password);
await smtp.SendAsync(email);
smtp.Disconnect(true);
}
}
}
appsettings.json
"MailSettings": {
"Mail": "[email protected]",
"DisplayName": "My mail",
"Password": "application_pass",
"Host": "smtp.gmail.com",
"Port": 465
},
docker-compose file
version: "3.8"
services:
api:
container_name: api
build: ./Api
depends_on:
- db
restart: unless-stopped
environment:
ASPNETCORE_URLS: http://+:5000
volumes:
- ./Images:/app/Images
db:
container_name: db
image: postgres
restart: always
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: api_db
volumes:
- ./postgres-data:/var/lib/postgresql/data:rw
client:
container_name: client
build: ./client
depends_on:
- api
restart: unless-stopped
client-admin:
container_name: client-admin
build: ./client-admin
depends_on:
- api
restart: unless-stopped
stdin_open: true
nginx:
image: nginx:stable-alpine
container_name: docker-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf.prod:/etc/nginx/conf.d/nginx