join_server.go 4.64 KB
Newer Older
1
package js
2
3

import (
4
	"context"
5
6
	"crypto/tls"
	"crypto/x509"
Orne Brocaar's avatar
Orne Brocaar committed
7
	"encoding/hex"
8
9
10
	"io/ioutil"
	"net/http"

11
	"github.com/pkg/errors"
12
13
	log "github.com/sirupsen/logrus"

14
15
	"github.com/brocaar/chirpstack-application-server/internal/config"
	"github.com/brocaar/chirpstack-application-server/internal/storage"
Orne Brocaar's avatar
Orne Brocaar committed
16
17
	"github.com/brocaar/lorawan"
	"github.com/brocaar/lorawan/backend/joinserver"
Gaël Berthaud-Müller's avatar
Gaël Berthaud-Müller committed
18
19

	"gitlab.rd.nic.fr/dance/dance"
20
21
)

22
23
24
25
26
var (
	bind    string
	caCert  string
	tlsCert string
	tlsKey  string
Gaël Berthaud-Müller's avatar
Gaël Berthaud-Müller committed
27
28
	enableDane bool
	resolvConf string
29
	allowedDomains []string
30
31
32
33
34
35
36
37
)

// Setup configures the package.
func Setup(conf config.Config) error {
	bind = conf.JoinServer.Bind
	caCert = conf.JoinServer.CACert
	tlsCert = conf.JoinServer.TLSCert
	tlsKey = conf.JoinServer.TLSKey
Gaël Berthaud-Müller's avatar
Gaël Berthaud-Müller committed
38
39
	enableDane = conf.JoinServer.EnableDane
	resolvConf = conf.JoinServer.ResolvConf
40
	allowedDomains = conf.JoinServer.AllowedDomains
41
42
43
44
45
46
47
48

	log.WithFields(log.Fields{
		"bind":     bind,
		"ca_cert":  caCert,
		"tls_cert": tlsCert,
		"tls_key":  tlsKey,
	}).Info("api/js: starting join-server api")

Orne Brocaar's avatar
Orne Brocaar committed
49
50
51
52
53
	handler, err := getHandler(conf)
	if err != nil {
		return errors.Wrap(err, "get join-server handler error")
	}

54
	server := http.Server{
Orne Brocaar's avatar
Orne Brocaar committed
55
		Handler:   handler,
56
57
58
59
60
61
62
63
64
65
66
67
		Addr:      bind,
		TLSConfig: &tls.Config{},
	}

	if caCert == "" && tlsCert == "" && tlsKey == "" {
		go func() {
			err := server.ListenAndServe()
			log.WithError(err).Fatal("join-server api error")
		}()
		return nil
	}

Gaël Berthaud-Müller's avatar
Gaël Berthaud-Müller committed
68
69
	if enableDane {
		go func() {
70
71
72
73
74
75
76
77
78
			danceConfig := dance.Config {
				CertFile: tlsCert,
				KeyFile: tlsKey,
				ResolverConf: resolvConf,
			}
			if len(allowedDomains) > 0 {
				danceConfig.AuthorizationCallback = dance.GetDomainAllowListCallback(allowedDomains)
			}
			err := dance.HttpServeAndListen(bind, &danceConfig, handler)
Gaël Berthaud-Müller's avatar
Gaël Berthaud-Müller committed
79
80
81
82
83
			log.WithError(err).Fatal("api/js: join-server api error")
		}()

		return nil
	} else if caCert != "" {
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
		caCert, err := ioutil.ReadFile(caCert)
		if err != nil {
			return errors.Wrap(err, "read ca certificate error")
		}

		caCertPool := x509.NewCertPool()
		if !caCertPool.AppendCertsFromPEM(caCert) {
			return errors.New("append ca certificate error")
		}

		server.TLSConfig.ClientCAs = caCertPool
		server.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert

		log.WithFields(log.Fields{
			"ca_cert": caCert,
		}).Info("api/js: join-server is configured with client-certificate authentication")
	}

	go func() {
		err := server.ListenAndServeTLS(tlsCert, tlsKey)
		log.WithError(err).Fatal("api/js: join-server api error")
	}()

	return nil
}
Orne Brocaar's avatar
Orne Brocaar committed
109
110
111
112
func getHandler(conf config.Config) (http.Handler, error) {
	jsConf := joinserver.HandlerConfig{
		Logger: log.StandardLogger(),
		GetDeviceKeysByDevEUIFunc: func(devEUI lorawan.EUI64) (joinserver.DeviceKeys, error) {
113
			dk, err := storage.GetDeviceKeys(context.TODO(), storage.DB(), devEUI)
Orne Brocaar's avatar
Orne Brocaar committed
114
115
116
117
118
119
120
121
			if err != nil {
				return joinserver.DeviceKeys{}, errors.Wrap(err, "get device-keys error")
			}

			if dk.JoinNonce == (1<<24)-1 {
				return joinserver.DeviceKeys{}, errors.New("join-nonce overflow")
			}
			dk.JoinNonce++
122
			if err := storage.UpdateDeviceKeys(context.TODO(), storage.DB(), &dk); err != nil {
Orne Brocaar's avatar
Orne Brocaar committed
123
124
125
126
127
128
				return joinserver.DeviceKeys{}, errors.Wrap(err, "update device-keys error")
			}

			return joinserver.DeviceKeys{
				DevEUI:    dk.DevEUI,
				NwkKey:    dk.NwkKey,
129
				AppKey:    dk.AppKey,
Orne Brocaar's avatar
Orne Brocaar committed
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
				JoinNonce: dk.JoinNonce,
			}, nil
		},
		GetKEKByLabelFunc: func(label string) ([]byte, error) {
			for _, kek := range conf.JoinServer.KEK.Set {
				if label == kek.Label {
					b, err := hex.DecodeString(kek.KEK)
					if err != nil {
						return nil, errors.Wrap(err, "decode hex encoded kek error")
					}

					return b, nil
				}
			}

			return nil, nil
		},
		GetASKEKLabelByDevEUIFunc: func(devEUI lorawan.EUI64) (string, error) {
			return conf.JoinServer.KEK.ASKEKLabel, nil
		},
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
		GetHomeNetIDByDevEUIFunc: func(devEUI lorawan.EUI64) (lorawan.NetID, error) {
			d, err := storage.GetDevice(context.TODO(), storage.DB(), devEUI, false, true)
			if err != nil {
				if errors.Cause(err) == storage.ErrDoesNotExist {
					return lorawan.NetID{}, joinserver.ErrDevEUINotFound
				}

				return lorawan.NetID{}, errors.Wrap(err, "get device error")
			}

			var netID lorawan.NetID

			netIDStr, ok := d.Variables.Map["home_netid"]
			if !ok {
				return netID, nil
			}

			if err := netID.UnmarshalText([]byte(netIDStr.String)); err != nil {
				return lorawan.NetID{}, errors.Wrap(err, "unmarshal netid error")
			}

			return netID, nil
		},
173
174
	}

175
176
177
178
179
180
181
182
183
	handler, err := joinserver.NewHandler(jsConf)
	if err != nil {
		return nil, errors.Wrap(err, "new join-server handler error")
	}

	return &prometheusMiddleware{
		handler:         handler,
		timingHistogram: conf.Metrics.Prometheus.APITimingHistogram,
	}, nil
Orne Brocaar's avatar
Orne Brocaar committed
184
}