Skip to content

Commit c004521

Browse files
kanchanavelusamyniranjanivivek
authored andcommitted
Implements the frontend logic for gNSI Authz
Signed-off-by: kanchanavelusamy <velusamyk@google.com>
1 parent d297045 commit c004521

File tree

10 files changed

+1427
-20
lines changed

10 files changed

+1427
-20
lines changed

gnmi_server/gnsi_authz.go

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
package gnmi
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
log "github.com/golang/glog"
9+
"github.com/openconfig/gnsi/authz"
10+
"google.golang.org/grpc/codes"
11+
"google.golang.org/grpc/status"
12+
"io"
13+
"os"
14+
"path/filepath"
15+
"strconv"
16+
"sync"
17+
"time"
18+
)
19+
20+
var (
21+
authzMu sync.Mutex
22+
)
23+
24+
var (
25+
// Step 1: Point the variable to the real function by default
26+
authenticateFunc = authenticate
27+
)
28+
29+
const (
30+
authzP4rtTbl string = "AUTHZ_POLICY|p4rt"
31+
authzGnxiTbl string = "AUTHZ_POLICY|gnxi"
32+
authzVersionFld string = "authz_version"
33+
authzCreatedOnFld string = "authz_created_on"
34+
)
35+
36+
type GNSIAuthzServer struct {
37+
*Server
38+
authzMetadata *AuthzMetadata
39+
authzMetadataCopy AuthzMetadata
40+
authz.UnimplementedAuthzServer
41+
}
42+
43+
func (srv *GNSIAuthzServer) Probe(context.Context, *authz.ProbeRequest) (*authz.ProbeResponse, error) {
44+
return nil, status.Errorf(codes.Unimplemented, "method Probe not implemented")
45+
}
46+
func (srv *GNSIAuthzServer) Get(context.Context, *authz.GetRequest) (*authz.GetResponse, error) {
47+
return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
48+
}
49+
func NewGNSIAuthzServer(srv *Server) *GNSIAuthzServer {
50+
ret := &GNSIAuthzServer{
51+
Server: srv,
52+
authzMetadata: NewAuthzMetadata(),
53+
}
54+
log.V(2).Infof("gnsi: loading authz metadata from %s", srv.config.AuthzMetaFile)
55+
log.V(2).Infof("gnsi: loading authz policy from %s", srv.config.AuthzPolicyFile)
56+
if err := ret.loadAuthzFreshness(srv.config.AuthzMetaFile); err != nil {
57+
log.V(0).Info(err)
58+
}
59+
ret.authzMetadataCopy = *ret.authzMetadata
60+
ret.writeAuthzMetadataToDB(authzVersionFld, ret.authzMetadata.AuthzVersion)
61+
ret.writeAuthzMetadataToDB(authzCreatedOnFld, ret.authzMetadata.AuthzCreatedOn)
62+
return ret
63+
}
64+
65+
// Rotate implements the gNSI.authz.Rotate RPC.
66+
func (srv *GNSIAuthzServer) Rotate(stream authz.Authz_RotateServer) error {
67+
ctx := stream.Context()
68+
log.Infof("GNSI Authz Rotate RPC")
69+
_, err := authenticateFunc(srv.config, ctx, "gnoi", false)
70+
if err != nil {
71+
log.Errorf("authentication failed in Rotate RPC: %v", err)
72+
return err
73+
}
74+
session := time.Now().Nanosecond()
75+
// Concurrent Authz RPCs are not allowed.
76+
if !authzMu.TryLock() {
77+
log.V(0).Infof("[%v]gNSI: authz.Rotate already in use", session)
78+
return status.Errorf(codes.Aborted, "concurrent authz.Rotate RPCs are not allowed")
79+
}
80+
defer authzMu.Unlock()
81+
82+
log.V(2).Infof("[%v]gNSI: Begin authz.Rotate", session)
83+
defer log.V(2).Infof("[%v]gNSI: End authz.Rotate", session)
84+
85+
srv.checkpointAuthzFreshness()
86+
if err := srv.checkpointAuthzFile(); err != nil {
87+
log.V(0).Infof("Failure during Authz checkpoint: %v", err)
88+
}
89+
for {
90+
req, err := stream.Recv()
91+
if err == io.EOF {
92+
log.V(0).Infof("[%v]gNSI: Received unexpected EOF", session)
93+
// Connection closed without Finalize message. Revert all changes made until now.
94+
if err := copyFile(srv.config.AuthzPolicyFile+backupExt, srv.config.AuthzPolicyFile); err != nil {
95+
log.V(0).Infof("[%v]gnsi: failed to revert authz policy file (%v): %v", session, srv.config.AuthzPolicyFile, err)
96+
}
97+
srv.revertAuthzFileFreshness()
98+
return status.Errorf(codes.Aborted, "No Finalize message")
99+
}
100+
if err != nil {
101+
log.V(0).Infof("[%v]gnsi: while processing a rotate request got error: `%v`. Reverting to last good state.", session, err)
102+
// Connection closed without Finalize message. Revert all changes made until now.
103+
if err := copyFile(srv.config.AuthzPolicyFile+backupExt, srv.config.AuthzPolicyFile); err != nil {
104+
log.V(0).Infof("[%v]gnsi: failed to revert authz policy file (%v): %v", session, srv.config.AuthzPolicyFile, err)
105+
}
106+
srv.revertAuthzFileFreshness()
107+
return status.Errorf(codes.Aborted, err.Error())
108+
}
109+
if endReq := req.GetFinalizeRotation(); endReq != nil {
110+
// This is the last message. All changes are final.
111+
log.V(2).Infof("[%v]gNSI: Received Finalize: %v", session, endReq)
112+
srv.commitAuthzFileChanges()
113+
srv.saveAuthzFileFreshess(srv.config.AuthzMetaFile)
114+
return nil
115+
}
116+
resp, err := srv.processRotateRequest(req)
117+
if err != nil {
118+
log.V(0).Infof("[%v]gnsi: while processing a rotate request got error: `%v`. Reverting to last good state.", session, err)
119+
// Connection closed without Finalize message. Revert all changes made until now.
120+
if err := copyFile(srv.config.AuthzPolicyFile+backupExt, srv.config.AuthzPolicyFile); err != nil {
121+
log.V(0).Infof("[%v]gnsi: failed to revert authz policy file (%v): %v", session, srv.config.AuthzPolicyFile, err)
122+
}
123+
srv.revertAuthzFileFreshness()
124+
return err
125+
}
126+
if err := stream.Send(resp); err != nil {
127+
log.V(0).Infof("[%v]gnsi: while processing a rotate request got error: `%v`. Reverting to last good state.", session, err)
128+
// Connection closed without Finalize message. Revert all changes made until now.
129+
if err := copyFile(srv.config.AuthzPolicyFile+backupExt, srv.config.AuthzPolicyFile); err != nil {
130+
log.V(0).Infof("[%v]gnsi: failed to revert authz policy file (%v): %v", session, srv.config.AuthzPolicyFile, err)
131+
}
132+
srv.revertAuthzFileFreshness()
133+
return status.Errorf(codes.Aborted, err.Error())
134+
}
135+
}
136+
}
137+
138+
func (srv *GNSIAuthzServer) processRotateRequest(req *authz.RotateAuthzRequest) (*authz.RotateAuthzResponse, error) {
139+
policyReq := req.GetUploadRequest()
140+
if policyReq == nil {
141+
return nil, status.Errorf(codes.Aborted, `Unknown request: "%v"`, req)
142+
}
143+
log.V(2).Infof("received a gNSI.Authz UploadRequest request message")
144+
log.V(3).Infof("request message: %v", policyReq)
145+
if len(policyReq.GetPolicy()) == 0 {
146+
return nil, status.Errorf(codes.Aborted, "Authz policy cannot be empty!")
147+
}
148+
if len(policyReq.GetVersion()) == 0 {
149+
return nil, status.Errorf(codes.Aborted, "Authz policy version cannot be empty!")
150+
}
151+
if !json.Valid([]byte(policyReq.GetPolicy())) {
152+
return nil, status.Errorf(codes.Aborted, "Authz policy `%v` is malformed", policyReq.GetPolicy())
153+
}
154+
if err := fileCheck(srv.config.AuthzPolicyFile); err != nil {
155+
return nil, status.Errorf(codes.NotFound, "Error in reading file %s: %v. Please try Install.", srv.config.AuthzPolicyFile, err)
156+
}
157+
if srv.gnsiAuthz.authzMetadata.AuthzVersion == policyReq.GetVersion() && !req.GetForceOverwrite() {
158+
return nil, status.Errorf(codes.AlreadyExists, "Authz with version `%v` already exists", policyReq.GetVersion())
159+
}
160+
if err := srv.writeAuthzMetadataToDB(authzVersionFld, policyReq.GetVersion()); err != nil {
161+
return nil, status.Errorf(codes.Aborted, err.Error())
162+
}
163+
if err := srv.writeAuthzMetadataToDB(authzCreatedOnFld, strconv.FormatUint(policyReq.GetCreatedOn(), 10)); err != nil {
164+
return nil, status.Errorf(codes.Aborted, err.Error())
165+
}
166+
if err := srv.saveToAuthzFile(policyReq.GetPolicy()); err != nil {
167+
return nil, status.Errorf(codes.Aborted, err.Error())
168+
}
169+
resp := &authz.RotateAuthzResponse{
170+
RotateResponse: &authz.RotateAuthzResponse_UploadResponse{},
171+
}
172+
return resp, nil
173+
}
174+
175+
func (srv *GNSIAuthzServer) saveToAuthzFile(p string) error {
176+
tmpDst, err := os.CreateTemp(filepath.Dir(srv.config.AuthzPolicyFile), filepath.Base(srv.config.AuthzPolicyFile))
177+
if err != nil {
178+
return err
179+
}
180+
if _, err := tmpDst.Write([]byte(p)); err != nil {
181+
if e := os.Remove(tmpDst.Name()); e != nil {
182+
log.V(1).Infof("Failed to cleanup file: %v: %v", tmpDst.Name(), e)
183+
}
184+
return err
185+
}
186+
if err := tmpDst.Close(); err != nil {
187+
if e := os.Remove(tmpDst.Name()); e != nil {
188+
log.V(1).Infof("Failed to cleanup file: %v: %v", tmpDst.Name(), e)
189+
}
190+
return err
191+
}
192+
if err := os.Rename(tmpDst.Name(), srv.config.AuthzPolicyFile); err != nil {
193+
if e := os.Remove(tmpDst.Name()); e != nil {
194+
log.V(1).Infof("Failed to cleanup file: %v: %v", tmpDst.Name(), e)
195+
}
196+
return err
197+
}
198+
return os.Chmod(srv.config.AuthzPolicyFile, 0600)
199+
}
200+
201+
func (srv *GNSIAuthzServer) checkpointAuthzFile() error {
202+
log.V(2).Infof("Checkpoint authz file: %v", srv.config.AuthzPolicyFile)
203+
return copyFile(srv.config.AuthzPolicyFile, srv.config.AuthzPolicyFile+backupExt)
204+
}
205+
206+
func (srv *GNSIAuthzServer) commitAuthzFileChanges() error {
207+
// Check if the active policy file exists.
208+
srcStat, err := os.Stat(srv.config.AuthzPolicyFile)
209+
if err != nil {
210+
return err
211+
}
212+
if !srcStat.Mode().IsRegular() {
213+
return fmt.Errorf("%s is not a regular file", srv.config.AuthzPolicyFile)
214+
}
215+
// OK. Now the backup can be deleted.
216+
backup := srv.config.AuthzPolicyFile + backupExt
217+
backupStat, err := os.Stat(backup)
218+
if err != nil {
219+
// Already does not exist.
220+
return nil
221+
}
222+
if !backupStat.Mode().IsRegular() {
223+
return fmt.Errorf("%s is not a regular file; did not remove it.", backup)
224+
}
225+
return os.Remove(backup)
226+
}
227+
228+
// writeAuthzMetadataToDB writes the credentials freshness data to the DB.
229+
func (srv *GNSIAuthzServer) writeAuthzMetadataToDB(fld, val string) error {
230+
if err := writeCredentialsMetadataToDB(authzP4rtTbl, "", fld, val); err != nil {
231+
return err
232+
}
233+
if err := writeCredentialsMetadataToDB(authzGnxiTbl, "", fld, val); err != nil {
234+
return err
235+
}
236+
switch fld {
237+
case authzVersionFld:
238+
srv.authzMetadata.AuthzVersion = val
239+
case authzCreatedOnFld:
240+
srv.authzMetadata.AuthzCreatedOn = val
241+
}
242+
return nil
243+
}
244+
245+
type AuthzMetadata struct {
246+
AuthzVersion string `json:"authz_version"`
247+
AuthzCreatedOn string `json:"authz_created_on"`
248+
}
249+
250+
func NewAuthzMetadata() *AuthzMetadata {
251+
return &AuthzMetadata{
252+
AuthzVersion: "unknown",
253+
AuthzCreatedOn: "0",
254+
}
255+
}
256+
257+
func (srv *GNSIAuthzServer) checkpointAuthzFreshness() {
258+
log.V(2).Infof("checkpoint authz freshness")
259+
srv.authzMetadataCopy = *srv.authzMetadata
260+
}
261+
262+
func (srv *GNSIAuthzServer) revertAuthzFileFreshness() {
263+
log.V(2).Infof("revert authz freshness")
264+
srv.writeAuthzMetadataToDB(authzVersionFld, srv.authzMetadataCopy.AuthzVersion)
265+
srv.writeAuthzMetadataToDB(authzCreatedOnFld, srv.authzMetadataCopy.AuthzCreatedOn)
266+
}
267+
268+
func (srv *GNSIAuthzServer) saveAuthzFileFreshess(path string) error {
269+
log.V(2).Infof("save authz metadata to file: %v", path)
270+
buf := new(bytes.Buffer)
271+
enc := json.NewEncoder(buf)
272+
if err := enc.Encode(*srv.authzMetadata); err != nil {
273+
log.V(0).Info(err)
274+
return err
275+
}
276+
if err := os.WriteFile(path, buf.Bytes(), 0644); err != nil {
277+
if e := os.Remove(path); e != nil {
278+
err = fmt.Errorf("Write %s failed: %w; Cleanup failed", path, err)
279+
}
280+
return err
281+
}
282+
return nil
283+
}
284+
285+
func (srv *GNSIAuthzServer) loadAuthzFreshness(path string) error {
286+
log.V(2).Infof("load authz metadata from file: %v", path)
287+
bytes, err := os.ReadFile(path)
288+
if err != nil {
289+
return err
290+
}
291+
return json.Unmarshal(bytes, srv.authzMetadata)
292+
}

0 commit comments

Comments
 (0)