aboutsummaryrefslogtreecommitdiff
path: root/Plugins/SessionCacheServer/Endpoints/BrokerHeartBeat.cs
blob: d501fca4b57978cb120c28a96d17e081c2e96536 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
using System;
using System.Net;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Security.Cryptography;

using VNLib.Data.Caching.Extensions;
using VNLib.Hashing.IdentityUtility;
using VNLib.Plugins.Essentials.Endpoints;
using VNLib.Plugins.Essentials.Extensions;
using VNLib.Plugins.Extensions.Loading;

namespace VNLib.Plugins.Essentials.Sessions.Server.Endpoints
{
    internal class BrokerHeartBeat : ResourceEndpointBase
    {
        public override string Path => "/heartbeat";

        private readonly Func<string> Token;
        private readonly ManualResetEvent KeepaliveSet;
        private readonly Task<IPAddress[]> BrokerIpList;
        private readonly PluginBase Pbase;

        protected override ProtectionSettings EndpointProtectionSettings { get; }

        public BrokerHeartBeat(Func<string> token, ManualResetEvent keepaliveSet, Uri brokerUri, PluginBase pbase)
        {
            Token = token;
            KeepaliveSet = keepaliveSet;
            BrokerIpList = Dns.GetHostAddressesAsync(brokerUri.DnsSafeHost);
            
            this.Pbase = pbase;

            EndpointProtectionSettings = new()
            {
                BrowsersOnly = false,
                SessionsRequired = false,
                VerifySessionCors = false,
            };
        }

        private async Task<byte[]> GetBrokerPubAsync()
        {
            string? brokerPubKey = await Pbase.TryGetSecretAsync("broker_public_key") ?? throw new KeyNotFoundException("Missing required secret : broker_public_key");

            return Convert.FromBase64String(brokerPubKey);
        }

        protected override async ValueTask<VfReturnType> GetAsync(HttpEntity entity)
        {
            //If-not loopback then verify server address
            if (!entity.Server.IsLoopBack())
            {
                //Load and verify the broker's ip address matches with an address we have stored
                IPAddress[] addresses = await BrokerIpList;
                if (!addresses.Contains(entity.TrustedRemoteIp))
                {
                    //Token invalid
                    entity.CloseResponse(HttpStatusCode.Forbidden);
                    return VfReturnType.VirtualSkip;
                }
            }
            //Get the authorization jwt
            string? jwtAuth = entity.Server.Headers[HttpRequestHeader.Authorization];
            if (string.IsNullOrWhiteSpace(jwtAuth))
            {
                //Token invalid
                entity.CloseResponse(HttpStatusCode.Forbidden);
                return VfReturnType.VirtualSkip;
            }
            //Parse the jwt
            using JsonWebToken jwt = JsonWebToken.Parse(jwtAuth);
            //Init signature alg
            using (ECDsa alg = ECDsa.Create(FBMDataCacheExtensions.CacheCurve))
            {
                //Get pub key
                byte[] key = await GetBrokerPubAsync();

                alg.ImportSubjectPublicKeyInfo(key, out _);
                //Verify the jwt
                if (!jwt.Verify(alg, FBMDataCacheExtensions.CacheJwtAlgorithm))
                {
                    //Token invalid
                    entity.CloseResponse(HttpStatusCode.Forbidden);
                    return VfReturnType.VirtualSkip;
                }
            }
            string? auth;
            //Recover the auth token from the jwt
            using (JsonDocument doc = jwt.GetPayload())
            {
                auth = doc.RootElement.GetProperty("token").GetString();
            }
            //Verify token
            if(Token().Equals(auth, StringComparison.Ordinal))
            {
                //Signal keepalive
                KeepaliveSet.Set();
                entity.CloseResponse(HttpStatusCode.OK);
                return VfReturnType.VirtualSkip;
            }
            //Token invalid
            entity.CloseResponse(HttpStatusCode.Forbidden);
            return VfReturnType.VirtualSkip;
        }
    }
}