diff --git a/cmd/keytransparency-monitor/Dockerfile b/cmd/keytransparency-monitor/Dockerfile new file mode 100644 index 000000000..bbd0dc8ad --- /dev/null +++ b/cmd/keytransparency-monitor/Dockerfile @@ -0,0 +1,10 @@ +FROM golang + +ADD keytransparency/genfiles/* /kt/ +ADD ./keytransparency /go/src/github.com/google/keytransparency +ADD ./trillian /go/src/github.com/google/trillian +WORKDIR /go/src/github.com/google/keytransparency + +ENTRYPOINT ["/go/bin/keytransparency-monitor"] + +EXPOSE 8099 diff --git a/cmd/keytransparency-monitor/main.go b/cmd/keytransparency-monitor/main.go new file mode 100644 index 000000000..179fa167a --- /dev/null +++ b/cmd/keytransparency-monitor/main.go @@ -0,0 +1,246 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "crypto/tls" + "flag" + "fmt" + "net" + "net/http" + "strings" + "time" + + "github.com/golang/glog" + "github.com/google/keytransparency/impl/monitor" + "github.com/google/trillian" + "github.com/google/trillian/crypto" + "github.com/google/trillian/crypto/keys/der" + "github.com/google/trillian/crypto/keys/pem" + "github.com/grpc-ecosystem/go-grpc-prometheus" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "golang.org/x/net/context" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/reflection" + + kpb "github.com/google/keytransparency/core/proto/keytransparency_v1_types" + spb "github.com/google/keytransparency/impl/proto/keytransparency_v1_service" + mopb "github.com/google/keytransparency/impl/proto/monitor_v1_service" + mupb "github.com/google/keytransparency/impl/proto/mutation_v1_service" +) + +var ( + addr = flag.String("addr", ":8099", "The ip:port combination to listen on") + keyFile = flag.String("tls-key", "genfiles/server.key", "TLS private key file") + certFile = flag.String("tls-cert", "genfiles/server.pem", "TLS cert file") + + autoConfig = flag.Bool("autoconfig", true, "Fetch config info from the server's /v1/domain/info") + mapKey = flag.String("map-key", "genfiles/map-rpc-server.pubkey.pem", "Path to public key PEM used to verify the SMH signature") + logKey = flag.String("log-key", "genfiles/log-rpc-server.pubkey.pem", "Path to public key PEM used to verify the STH signature") + + signingKey = flag.String("sign-key", "genfiles/monitor_sign-key.pem", "Path to private key PEM for SMH signing") + signingKeyPassword = flag.String("password", "towel", "Password of the private key PEM file for SMH signing") + ktURL = flag.String("kt-url", "localhost:8080", "URL of key-server.") + insecure = flag.Bool("insecure", false, "Skip TLS checks") + ktCert = flag.String("kt-cert", "genfiles/server.crt", "Path to kt-server's public key") + + pollPeriod = flag.Duration("poll-period", time.Second*5, "Maximum time between polling the key-server. Ideally, this is equal to the min-period of paramerter of the keyserver.") + + // TODO(ismail): expose prometheus metrics: a variable that tracks valid/invalid MHs + metricsAddr = flag.String("metrics-addr", ":8081", "The ip:port to publish metrics on") +) + +func grpcGatewayMux(addr string) (*runtime.ServeMux, error) { + ctx := context.Background() + creds, err := credentials.NewClientTLSFromFile(*certFile, "") + if err != nil { + return nil, err + } + dopts := []grpc.DialOption{grpc.WithTransportCredentials(creds)} + gwmux := runtime.NewServeMux() + if err := mopb.RegisterMonitorServiceHandlerFromEndpoint(ctx, gwmux, addr, dopts); err != nil { + return nil, err + } + + return gwmux, nil +} + +// grpcHandlerFunc returns an http.Handler that delegates to grpcServer on incoming gRPC +// connections or otherHandler otherwise. Copied from cockroachdb. +func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // This is a partial recreation of gRPC's internal checks. + // https://github.com/grpc/grpc-go/blob/master/transport/handler_server.go#L62 + if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { + grpcServer.ServeHTTP(w, r) + } else { + otherHandler.ServeHTTP(w, r) + } + }) +} + +func main() { + flag.Parse() + + creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile) + if err != nil { + glog.Exitf("Failed to load server credentials %v", err) + } + + // Create gRPC server. + grpcServer := grpc.NewServer( + grpc.Creds(creds), + grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor), + grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor), + ) + + // Connect to the kt-server's mutation API: + grpcc, err := dial(*ktURL) + if err != nil { + glog.Fatalf("Error Dialing %v: %v", ktURL, err) + } + mcc := mupb.NewMutationServiceClient(grpcc) + + // Read signing key: + key, err := pem.ReadPrivateKeyFile(*signingKey, *signingKeyPassword) + if err != nil { + glog.Fatalf("Could not create signer from %v: %v", *signingKey, err) + } + ctx := context.Background() + logTree, mapTree, err := getTrees(ctx, grpcc) + if err != nil { + glog.Fatalf("Could not read domain info %v:", err) + } + + srv := monitor.New(mcc, crypto.NewSHA256Signer(key), logTree, mapTree, *pollPeriod) + + mopb.RegisterMonitorServiceServer(grpcServer, srv) + reflection.Register(grpcServer) + grpc_prometheus.Register(grpcServer) + grpc_prometheus.EnableHandlingTimeHistogram() + + // Create HTTP handlers and gRPC gateway. + gwmux, err := grpcGatewayMux(*addr) + if err != nil { + glog.Exitf("Failed setting up REST proxy: %v", err) + } + + // Insert handlers for other http paths here. + mux := http.NewServeMux() + mux.Handle("/", gwmux) + + go func() { + if err := srv.StartPolling(); err != nil { + glog.Fatalf("Could not start polling mutations.") + } + }() + + // Serve HTTP2 server over TLS. + glog.Infof("Listening on %v", *addr) + if err := http.ListenAndServeTLS(*addr, *certFile, *keyFile, + grpcHandlerFunc(grpcServer, mux)); err != nil { + glog.Errorf("ListenAndServeTLS: %v", err) + } +} + +func dial(ktURL string) (*grpc.ClientConn, error) { + var opts []grpc.DialOption + + transportCreds, err := transportCreds(ktURL, *ktCert, *insecure) + if err != nil { + return nil, err + } + opts = append(opts, grpc.WithTransportCredentials(transportCreds)) + + // TODO(ismail): authenticate the monitor to the kt-server: + cc, err := grpc.Dial(ktURL, opts...) + if err != nil { + return nil, err + } + return cc, nil +} + +// TODO(ismail): refactor client and monitor to use the same methods +func transportCreds(ktURL string, ktCert string, insecure bool) (credentials.TransportCredentials, error) { + // copied from keytransparency-client/cmd/root.go: transportCreds + host, _, err := net.SplitHostPort(ktURL) + if err != nil { + return nil, err + } + + switch { + case insecure: // Impatient insecure. + return credentials.NewTLS(&tls.Config{ + InsecureSkipVerify: true, + }), nil + + case ktCert != "": // Custom CA Cert. + return credentials.NewClientTLSFromFile(ktCert, host) + + default: // Use the local set of root certs. + return credentials.NewClientTLSFromCert(nil, host), nil + } +} + +// config selects a source for and returns the client configuration. +func getTrees(ctx context.Context, cc *grpc.ClientConn) (mapTree *trillian.Tree, logTree *trillian.Tree, err error) { + switch { + case *autoConfig: + ktClient := spb.NewKeyTransparencyServiceClient(cc) + resp, err2 := ktClient.GetDomainInfo(ctx, &kpb.GetDomainInfoRequest{}) + if err2 != nil { + err = err2 + return + } + logTree = resp.GetLog() + mapTree = resp.GetMap() + return + default: + return readConfigFromDisk() + } +} + +func readConfigFromDisk() (mapTree *trillian.Tree, logTree *trillian.Tree, err error) { + // Log PubKey. + logPubKey, err := pem.ReadPublicKeyFile(*logKey) + if err != nil { + return nil, nil, fmt.Errorf("Failed to open log public key %v: %v", *logKey, err) + } + logPubPB, err := der.ToPublicProto(logPubKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to serialize log public key: %v", err) + } + + // MapPubKey. + mapPubKey, err := pem.ReadPublicKeyFile(*mapKey) + if err != nil { + return nil, nil, fmt.Errorf("error reading map public key %v: %v", *mapKey, err) + } + mapPubPB, err := der.ToPublicProto(mapPubKey) + if err != nil { + return nil, nil, fmt.Errorf("error seralizeing map public key: %v", err) + } + logTree = &trillian.Tree{ + HashStrategy: trillian.HashStrategy_OBJECT_RFC6962_SHA256, + PublicKey: logPubPB, + } + mapTree = &trillian.Tree{ + HashStrategy: trillian.HashStrategy_CONIKS_SHA512_256, + PublicKey: mapPubPB, + } + return +} diff --git a/core/monitor/monitor.go b/core/monitor/monitor.go new file mode 100644 index 000000000..2e3ea481d --- /dev/null +++ b/core/monitor/monitor.go @@ -0,0 +1,45 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package monitor + +import ( + "crypto" + "fmt" + + "github.com/google/trillian" + "github.com/google/trillian/merkle" + "github.com/google/trillian/merkle/hashers" +) + +type Monitor struct { + hasher hashers.MapHasher + logPubKey crypto.PublicKey + mapPubKey crypto.PublicKey + logVerifier merkle.LogVerifier + trusted trillian.SignedLogRoot +} + +func New(logTree, mapTree trillian.Tree) (*Monitor, error) { + // Log Hasher. + logHasher, err := hashers.NewLogHasher(logTree.GetHashStrategy()) + if err != nil { + return nil, fmt.Errorf("Failed creating LogHasher: %v", err) + } + return &Monitor{ + logVerifier: merkle.NewLogVerifier(logHasher), + logPubKey: logTree.GetPublicKey(), + mapPubKey: mapTree.GetPublicKey(), + }, nil +} diff --git a/core/monitor/verify.go b/core/monitor/verify.go new file mode 100644 index 000000000..c3d057a85 --- /dev/null +++ b/core/monitor/verify.go @@ -0,0 +1,165 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package monitor implements the monitor service. A monitor repeatedly polls a +// key-transparency server's Mutations API and signs Map Roots if it could +// reconstruct clients can query. +package monitor + +import ( + "bytes" + "errors" + "fmt" + "math/big" + + "github.com/golang/glog" + + "github.com/google/trillian/merkle" + "github.com/google/trillian/merkle/coniks" + "github.com/google/trillian/storage" + + tcrypto "github.com/google/trillian/crypto" + + "github.com/google/keytransparency/core/mutator/entry" + ktpb "github.com/google/keytransparency/core/proto/keytransparency_v1_types" + mopb "github.com/google/keytransparency/core/proto/monitor_v1_types" + "time" +) + +var ( + // ErrInvalidMutation occurs when verification failed because of an invalid + // mutation. + ErrInvalidMutation = errors.New("invalid mutation") + // ErrNotMatchingRoot occurs when the reconstructed root differs from the one + // we received from the server. + ErrNotMatchingRoot = errors.New("recreated root does not match") +) + +// verifyResponse verifies a response received by the GetMutations API. +// Additionally to the response it takes a complete list of mutations. The list +// of received mutations may differ from those included in the initial response +// because of the max. page size. +func (m *Monitor) VerifyResponse(in *ktpb.GetMutationsResponse, allMuts []*ktpb.Mutation) *mopb.GetMonitoringResponse { + resp := new(mopb.GetMonitoringResponse) + seen := time.Now().UnixNano() + resp.SeenTimestampNanos = seen + + // copy of received SMR: + smr := *in.Smr + resp.Smr = &smr + // reset map + resp.Smr.Signature = nil + + // verify signature on map root: + if err := tcrypto.VerifyObject(m.mapPubKey, resp.Smr, in.GetSmr().GetSignature()); err != nil { + glog.Errorf("couldn't verify signature on map root: %v", err) + resp.InvalidMapSigProof = in.GetSmr() + } + + logRoot := in.GetLogRoot() + // Verify SignedLogRoot signature. + hash := tcrypto.HashLogRoot(*logRoot) + if err := tcrypto.Verify(m.logPubKey, hash, logRoot.GetSignature()); err != nil { + resp.InvalidLogSigProof = logRoot + } + + // Implicitly trust the first root we get. + if m.trusted.TreeSize != 0 { + // Verify consistency proof. + if err := m.logVerifier.VerifyConsistencyProof( + m.trusted.TreeSize, logRoot.TreeSize, + m.trusted.RootHash, logRoot.RootHash, + in.GetLogConsistency()); err != nil { + // TODO + } + } + + // TODO: + // logVerifier.VerifyInclusionProof() + + if err := m.verifyMutations(allMuts, in.GetSmr().GetRootHash(), in.GetSmr().GetMapId()); err != nil { + // TODO resp.InvalidMutation + } + + return resp +} + +func (m *Monitor) verifyMutations(muts []*ktpb.Mutation, expectedRoot []byte, mapID int64) error { + newLeaves := make([]merkle.HStar2LeafHash, 0, len(muts)) + mutator := entry.New() + oldProofNodes := make(map[string][]byte) + hasher := coniks.Default + + for _, m := range muts { + // verify that the provided leaf’s inclusion proof goes to epoch e-1: + //if err := merkle.VerifyMapInclusionProof(mapID, index, + // leafHash, rootHash, proof, hasher); err != nil { + // glog.Errorf("VerifyMapInclusionProof(%x): %v", index, err) + // // TODO modify response object + //} + oldLeaf, err := entry.FromLeafValue(m.GetProof().GetLeaf().GetLeafValue()) + if err != nil { + return ErrInvalidMutation + } + + // compute the new leaf + newLeaf, err := mutator.Mutate(oldLeaf, m.GetUpdate()) + if err != nil { + // TODO(ismail): collect all data to reproduce this (expectedRoot, oldLeaf, and mutation) + return ErrInvalidMutation + } + index := m.GetProof().GetLeaf().GetIndex() + newLeafnID := storage.NewNodeIDFromPrefixSuffix(index, storage.Suffix{}, hasher.BitLen()) + newLeafHash := hasher.HashLeaf(mapID, index, newLeaf) + newLeaves = append(newLeaves, merkle.HStar2LeafHash{ + Index: newLeafnID.BigInt(), + LeafHash: newLeafHash, + }) + // store the proof hashes locally to recompute the tree below: + sibIDs := newLeafnID.Siblings() + for level, proof := range m.GetProof().GetInclusion() { + pID := sibIDs[level] + if p, ok := oldProofNodes[pID.String()]; ok { + // sanity check: for each mutation overlapping proof nodes should be + // equal: + if !bytes.Equal(p, proof) { + // TODO: this is really odd -> monitor should complain! + } + } else { + oldProofNodes[pID.String()] = proof + } + } + } + + // compute the new root using local intermediate hashes from epoch e + // (above proof hashes): + hs2 := merkle.NewHStar2(mapID, hasher) + newRoot, err := hs2.HStar2Nodes([]byte{}, hasher.Size(), newLeaves, + func(depth int, index *big.Int) ([]byte, error) { + nID := storage.NewNodeIDFromBigInt(depth, index, hasher.BitLen()) + if p, ok := oldProofNodes[nID.String()]; ok { + return p, nil + } + return nil, nil + }, nil) + if err != nil { + glog.Errorf("hs2.HStar2Nodes(_): %v", err) + fmt.Errorf("could not compute new root hash: hs2.HStar2Nodes(_): %v", err) + } + // verify rootHash + if !bytes.Equal(newRoot, expectedRoot) { + return ErrNotMatchingRoot + } + return nil +} diff --git a/core/monitor/verify_test.go b/core/monitor/verify_test.go new file mode 100644 index 000000000..7174c0851 --- /dev/null +++ b/core/monitor/verify_test.go @@ -0,0 +1,22 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package monitor + +// TODO(ismail): write extensive tests for verification steps (if not existing +// in trillian) +const ( + emptyMapRootB64 = "xmifEIEqCYCXbZUz2Dh1KCFmFZVn7DUVVxbBQTr1PWo=" + mapID = int64(0) +) diff --git a/core/mutation/mutation.go b/core/mutation/mutation.go index 2501ccdd7..a8fa2e548 100644 --- a/core/mutation/mutation.go +++ b/core/mutation/mutation.go @@ -212,9 +212,9 @@ func (s *Server) inclusionProofs(ctx context.Context, indexes [][]byte, epoch in glog.Errorf("GetLeaves(): %v", err) return nil, grpc.Errorf(codes.Internal, "Failed fetching map leaf") } - if got, want := len(getResp.MapLeafInclusion), len(indexes); got != want { + if got, want := len(getResp.GetMapLeafInclusion()), len(indexes); got != want { glog.Errorf("GetLeaves() len: %v, want %v", got, want) return nil, grpc.Errorf(codes.Internal, "Failed fetching map leaf") } - return getResp.MapLeafInclusion, nil + return getResp.GetMapLeafInclusion(), nil } diff --git a/core/proto/keytransparency_v1_types/keytransparency_v1_types.pb.go b/core/proto/keytransparency_v1_types/keytransparency_v1_types.pb.go index 4f6d61237..bfda36ebf 100644 --- a/core/proto/keytransparency_v1_types/keytransparency_v1_types.pb.go +++ b/core/proto/keytransparency_v1_types/keytransparency_v1_types.pb.go @@ -499,7 +499,7 @@ type ListEntryHistoryRequest struct { // app_id is the identifier for the application. AppId string `protobuf:"bytes,4,opt,name=app_id,json=appId" json:"app_id,omitempty"` // first_tree_size is the tree_size of the currently trusted log root. - // Omitting this field will ommit the log consistency proof from the response. + // Omitting this field will omit the log consistency proof from the response. FirstTreeSize int64 `protobuf:"varint,5,opt,name=first_tree_size,json=firstTreeSize" json:"first_tree_size,omitempty"` } @@ -618,7 +618,7 @@ func (m *UpdateEntryRequest) GetEntryUpdate() *EntryUpdate { } // UpdateEntryResponse contains a proof once the update has been included in -// the Merkel Tree. +// the Merkle Tree. type UpdateEntryResponse struct { // proof contains a proof that the update has been included in the tree. Proof *GetEntryResponse `protobuf:"bytes,1,opt,name=proof" json:"proof,omitempty"` diff --git a/core/proto/keytransparency_v1_types/keytransparency_v1_types.proto b/core/proto/keytransparency_v1_types/keytransparency_v1_types.proto index f406f5c8e..0ab220e7d 100644 --- a/core/proto/keytransparency_v1_types/keytransparency_v1_types.proto +++ b/core/proto/keytransparency_v1_types/keytransparency_v1_types.proto @@ -169,7 +169,7 @@ message ListEntryHistoryRequest { // app_id is the identifier for the application. string app_id = 4; // first_tree_size is the tree_size of the currently trusted log root. - // Omitting this field will ommit the log consistency proof from the response. + // Omitting this field will omit the log consistency proof from the response. int64 first_tree_size = 5; } @@ -196,7 +196,7 @@ message UpdateEntryRequest { } // UpdateEntryResponse contains a proof once the update has been included in -// the Merkel Tree. +// the Merkle Tree. message UpdateEntryResponse { // proof contains a proof that the update has been included in the tree. GetEntryResponse proof = 1; @@ -250,4 +250,4 @@ message GetDomainInfoResponse { trillian.Tree map = 2; // Vrf contains the VRF public key. keyspb.PublicKey vrf = 3; -} +} \ No newline at end of file diff --git a/core/proto/monitor_v1_types/gen.go b/core/proto/monitor_v1_types/gen.go new file mode 100644 index 000000000..624949b57 --- /dev/null +++ b/core/proto/monitor_v1_types/gen.go @@ -0,0 +1,17 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:generate protoc -I=. -I=$GOPATH/src/ -I=$GOPATH/src/github.com/google/trillian/ -I=$GOPATH/src/github.com/googleapis/googleapis --go_out=:. monitor_v1_types.proto + +package monitor_v1_types diff --git a/core/proto/monitor_v1_types/monitor_v1_types.pb.go b/core/proto/monitor_v1_types/monitor_v1_types.pb.go new file mode 100644 index 000000000..6301d6f0a --- /dev/null +++ b/core/proto/monitor_v1_types/monitor_v1_types.pb.go @@ -0,0 +1,245 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: monitor_v1_types.proto + +/* +Package monitor_v1_types is a generated protocol buffer package. + +Key Transparency Monitor Service + + +It is generated from these files: + monitor_v1_types.proto + +It has these top-level messages: + GetMonitoringRequest + InvalidMutation + NotMatchingMapRootProof + GetMonitoringResponse +*/ +package monitor_v1_types + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import trillian "github.com/google/trillian" +import trillian1 "github.com/google/trillian" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// GetMonitoringRequest contains the input parameters of the GetMonitoring APIs. +type GetMonitoringRequest struct { + // start specifies the start epoch number from which monitoring results will + // be returned (ranging from [start, latestObserved] and starting at 1). + Start int64 `protobuf:"varint,1,opt,name=start" json:"start,omitempty"` +} + +func (m *GetMonitoringRequest) Reset() { *m = GetMonitoringRequest{} } +func (m *GetMonitoringRequest) String() string { return proto.CompactTextString(m) } +func (*GetMonitoringRequest) ProtoMessage() {} +func (*GetMonitoringRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *GetMonitoringRequest) GetStart() int64 { + if m != nil { + return m.Start + } + return 0 +} + +// InvalidMutation includes all information to reproduce that there was an +// invalid mutation from epoch e to e+1. +type InvalidMutation struct { + // old_leaf is the inclusion proof to the leaf at epoch e. + OldLeaf *trillian1.MapLeafInclusion `protobuf:"bytes,1,opt,name=old_leaf,json=oldLeaf" json:"old_leaf,omitempty"` + // new_leaf is the inclusion proof to the leaf at epoch e+1. + NewLeaf *trillian1.MapLeafInclusion `protobuf:"bytes,2,opt,name=new_leaf,json=newLeaf" json:"new_leaf,omitempty"` +} + +func (m *InvalidMutation) Reset() { *m = InvalidMutation{} } +func (m *InvalidMutation) String() string { return proto.CompactTextString(m) } +func (*InvalidMutation) ProtoMessage() {} +func (*InvalidMutation) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *InvalidMutation) GetOldLeaf() *trillian1.MapLeafInclusion { + if m != nil { + return m.OldLeaf + } + return nil +} + +func (m *InvalidMutation) GetNewLeaf() *trillian1.MapLeafInclusion { + if m != nil { + return m.NewLeaf + } + return nil +} + +// NotMatchingMapRootProof contains all data necessary to reproduce that set of +// mutations does not produce new expected map root. +type NotMatchingMapRootProof struct { + // map_root contains the map root hash the monitor observed. + MapRoot *trillian.SignedMapRoot `protobuf:"bytes,1,opt,name=map_root,json=mapRoot" json:"map_root,omitempty"` + // old_leafs is a list of inclusion proofs for the leafs in epoch e. + OldLeafs []*trillian1.MapLeafInclusion `protobuf:"bytes,2,rep,name=old_leafs,json=oldLeafs" json:"old_leafs,omitempty"` + // new_leafs is a list of inclusion proofs for changed leafs (from epoch e + // to epoch e+1). Hashing these produces a different hash than root hash in + // above's map_root. + NewLeafs []*trillian1.MapLeafInclusion `protobuf:"bytes,3,rep,name=new_leafs,json=newLeafs" json:"new_leafs,omitempty"` +} + +func (m *NotMatchingMapRootProof) Reset() { *m = NotMatchingMapRootProof{} } +func (m *NotMatchingMapRootProof) String() string { return proto.CompactTextString(m) } +func (*NotMatchingMapRootProof) ProtoMessage() {} +func (*NotMatchingMapRootProof) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +func (m *NotMatchingMapRootProof) GetMapRoot() *trillian.SignedMapRoot { + if m != nil { + return m.MapRoot + } + return nil +} + +func (m *NotMatchingMapRootProof) GetOldLeafs() []*trillian1.MapLeafInclusion { + if m != nil { + return m.OldLeafs + } + return nil +} + +func (m *NotMatchingMapRootProof) GetNewLeafs() []*trillian1.MapLeafInclusion { + if m != nil { + return m.NewLeafs + } + return nil +} + +type GetMonitoringResponse struct { + // smr contains the map root for the sparse Merkle Tree signed with the + // monitor's key on success. If the checks were not successful the + // smr will be empty. The epochs are encoded into the smr map_revision. + Smr *trillian.SignedMapRoot `protobuf:"bytes,1,opt,name=smr" json:"smr,omitempty"` + // seen_timestamp_nanos contains the time in nanoseconds where this particular + // signed map root was retrieved and processed. The actual timestamp of the + // smr returned by the server is contained in above smr field. + SeenTimestampNanos int64 `protobuf:"varint,2,opt,name=seen_timestamp_nanos,json=seenTimestampNanos" json:"seen_timestamp_nanos,omitempty"` + // isValid signals if all verification steps for the requested epoch passed + // or not. + IsValid bool `protobuf:"varint,3,opt,name=isValid" json:"isValid,omitempty"` + // invalidMapSigProof contains the signed map root received by the + // key-transparency server, if and only if the key-server's signature was + // invalid. + InvalidMapSigProof *trillian.SignedMapRoot `protobuf:"bytes,4,opt,name=invalidMapSigProof" json:"invalidMapSigProof,omitempty"` + // invalidLogSigProof contains the signed map root received by the + // key-transparency server, if and only if the key-server's signature was + // invalid. + InvalidLogSigProof *trillian.SignedLogRoot `protobuf:"bytes,5,opt,name=invalidLogSigProof" json:"invalidLogSigProof,omitempty"` + // invalidMutation contains data to reproduce that there was an invalid + // mutation from epoch e to epoch e+1 + InvalidMutation *InvalidMutation `protobuf:"bytes,6,opt,name=invalidMutation" json:"invalidMutation,omitempty"` + // NotMatchingMapRoot contains all data to reproduce that the set of mutations + // does not produce observed new map root. + NotMatchingMapRoot *NotMatchingMapRootProof `protobuf:"bytes,7,opt,name=notMatchingMapRoot" json:"notMatchingMapRoot,omitempty"` +} + +func (m *GetMonitoringResponse) Reset() { *m = GetMonitoringResponse{} } +func (m *GetMonitoringResponse) String() string { return proto.CompactTextString(m) } +func (*GetMonitoringResponse) ProtoMessage() {} +func (*GetMonitoringResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +func (m *GetMonitoringResponse) GetSmr() *trillian.SignedMapRoot { + if m != nil { + return m.Smr + } + return nil +} + +func (m *GetMonitoringResponse) GetSeenTimestampNanos() int64 { + if m != nil { + return m.SeenTimestampNanos + } + return 0 +} + +func (m *GetMonitoringResponse) GetIsValid() bool { + if m != nil { + return m.IsValid + } + return false +} + +func (m *GetMonitoringResponse) GetInvalidMapSigProof() *trillian.SignedMapRoot { + if m != nil { + return m.InvalidMapSigProof + } + return nil +} + +func (m *GetMonitoringResponse) GetInvalidLogSigProof() *trillian.SignedLogRoot { + if m != nil { + return m.InvalidLogSigProof + } + return nil +} + +func (m *GetMonitoringResponse) GetInvalidMutation() *InvalidMutation { + if m != nil { + return m.InvalidMutation + } + return nil +} + +func (m *GetMonitoringResponse) GetNotMatchingMapRoot() *NotMatchingMapRootProof { + if m != nil { + return m.NotMatchingMapRoot + } + return nil +} + +func init() { + proto.RegisterType((*GetMonitoringRequest)(nil), "monitor.v1.types.GetMonitoringRequest") + proto.RegisterType((*InvalidMutation)(nil), "monitor.v1.types.InvalidMutation") + proto.RegisterType((*NotMatchingMapRootProof)(nil), "monitor.v1.types.NotMatchingMapRootProof") + proto.RegisterType((*GetMonitoringResponse)(nil), "monitor.v1.types.GetMonitoringResponse") +} + +func init() { proto.RegisterFile("monitor_v1_types.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 437 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0xc1, 0x6e, 0x13, 0x31, + 0x10, 0x86, 0x95, 0x2e, 0x6d, 0x82, 0x39, 0x14, 0x59, 0x81, 0xae, 0x72, 0x0a, 0x39, 0x35, 0x12, + 0xda, 0x92, 0x22, 0xc4, 0x23, 0x54, 0x15, 0xd9, 0x0a, 0xb9, 0x08, 0x89, 0xd3, 0xca, 0x4d, 0x1c, + 0x77, 0x24, 0xef, 0x8c, 0x59, 0x4f, 0x52, 0x71, 0xe2, 0xd1, 0xb8, 0xf2, 0x58, 0xc8, 0xbb, 0x9b, + 0xd0, 0xa6, 0x81, 0xf4, 0x66, 0xcf, 0xcc, 0xf7, 0xfb, 0xf7, 0x78, 0x2c, 0x5e, 0x97, 0x84, 0xc0, + 0x54, 0x15, 0xab, 0x49, 0xc1, 0x3f, 0xbc, 0x09, 0x99, 0xaf, 0x88, 0x49, 0xbe, 0x6c, 0xe3, 0xd9, + 0x6a, 0x92, 0xd5, 0xf1, 0xc1, 0xd8, 0x02, 0xdf, 0x2e, 0x6f, 0xb2, 0x19, 0x95, 0x67, 0x96, 0xc8, + 0x3a, 0x73, 0xc6, 0x15, 0x38, 0x07, 0x1a, 0x37, 0x8b, 0x06, 0x1e, 0x4c, 0x9e, 0x50, 0x5a, 0x94, + 0xda, 0x17, 0xda, 0x43, 0x83, 0x8c, 0xde, 0x8a, 0xfe, 0x85, 0xe1, 0xbc, 0x39, 0x14, 0xd0, 0x2a, + 0xf3, 0x7d, 0x69, 0x02, 0xcb, 0xbe, 0x38, 0x0c, 0xac, 0x2b, 0x4e, 0x3b, 0xc3, 0xce, 0x69, 0xa2, + 0x9a, 0xcd, 0xe8, 0xa7, 0x38, 0xbe, 0xc4, 0x95, 0x76, 0x30, 0xcf, 0x97, 0xac, 0x19, 0x08, 0xe5, + 0x07, 0xd1, 0x23, 0x37, 0x2f, 0x9c, 0xd1, 0x8b, 0xba, 0xf6, 0xc5, 0xf9, 0x20, 0xdb, 0xd8, 0xca, + 0xb5, 0x9f, 0x1a, 0xbd, 0xb8, 0xc4, 0x99, 0x5b, 0x06, 0x20, 0x54, 0x5d, 0x72, 0xf3, 0x18, 0x89, + 0x18, 0x9a, 0xbb, 0x06, 0x3b, 0xd8, 0x8f, 0xa1, 0xb9, 0x8b, 0x91, 0xd1, 0xaf, 0x8e, 0x38, 0xb9, + 0x22, 0xce, 0x35, 0xcf, 0x6e, 0x01, 0x6d, 0xae, 0xbd, 0x22, 0xe2, 0xcf, 0x15, 0xd1, 0x42, 0x9e, + 0x8b, 0x5e, 0xbc, 0x5b, 0x45, 0xc4, 0xad, 0x93, 0x93, 0xbf, 0x92, 0xd7, 0x60, 0xd1, 0xcc, 0xdb, + 0x7a, 0xd5, 0x2d, 0x9b, 0x85, 0xfc, 0x28, 0x9e, 0xaf, 0xdd, 0x87, 0xf4, 0x60, 0x98, 0xec, 0xf1, + 0xd1, 0x6b, 0xed, 0x87, 0x08, 0xae, 0xfd, 0x87, 0x34, 0xd9, 0x0f, 0xb6, 0x17, 0x08, 0xa3, 0xdf, + 0x89, 0x78, 0xb5, 0xd5, 0xf1, 0xe0, 0x09, 0x83, 0x91, 0x63, 0x91, 0x84, 0xb2, 0xda, 0x67, 0x3d, + 0xd6, 0xc8, 0x77, 0xa2, 0x1f, 0x8c, 0xc1, 0x82, 0xa1, 0x34, 0x81, 0x75, 0xe9, 0x0b, 0xd4, 0x48, + 0xa1, 0xee, 0x64, 0xa2, 0x64, 0xcc, 0x7d, 0x59, 0xa7, 0xae, 0x62, 0x46, 0xa6, 0xa2, 0x0b, 0xe1, + 0x6b, 0x7c, 0xb9, 0x34, 0x19, 0x76, 0x4e, 0x7b, 0x6a, 0xbd, 0x95, 0x17, 0x42, 0x42, 0xfb, 0xa6, + 0xda, 0x5f, 0x83, 0xad, 0x9b, 0x99, 0x3e, 0xfb, 0xbf, 0x8b, 0x1d, 0xc8, 0x3d, 0xa1, 0x29, 0xd9, + 0x8d, 0xd0, 0xe1, 0x6e, 0xa1, 0x29, 0xd9, 0x07, 0x42, 0xf7, 0x10, 0xf9, 0x49, 0x1c, 0xc3, 0xc3, + 0x29, 0x4b, 0x8f, 0x6a, 0x95, 0x37, 0xd9, 0xf6, 0xef, 0xc8, 0xb6, 0xc6, 0x51, 0x6d, 0x93, 0xf2, + 0x9b, 0x90, 0xf8, 0x68, 0x60, 0xd2, 0x6e, 0xad, 0x37, 0x7e, 0xac, 0xf7, 0x8f, 0xe1, 0x52, 0x3b, + 0x44, 0x6e, 0x8e, 0xea, 0x2f, 0xf4, 0xfe, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xdb, 0xda, 0xc5, + 0xc2, 0xcc, 0x03, 0x00, 0x00, +} diff --git a/core/proto/monitor_v1_types/monitor_v1_types.proto b/core/proto/monitor_v1_types/monitor_v1_types.proto new file mode 100644 index 000000000..16e0e4bbe --- /dev/null +++ b/core/proto/monitor_v1_types/monitor_v1_types.proto @@ -0,0 +1,86 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +// Key Transparency Monitor Service +// +package monitor.v1.types; + +import "github.com/google/trillian/trillian.proto"; +import "github.com/google/trillian/trillian_map_api.proto"; + +// GetMonitoringRequest contains the input parameters of the GetMonitoring APIs. +message GetMonitoringRequest { + // start specifies the start epoch number from which monitoring results will + // be returned (ranging from [start, latestObserved] and starting at 1). + int64 start = 1; +} + +// InvalidMutation includes all information to reproduce that there was an +// invalid mutation from epoch e to e+1. +message InvalidMutation { + // old_leaf is the inclusion proof to the leaf at epoch e. + trillian.MapLeafInclusion old_leaf = 1; + // new_leaf is the inclusion proof to the leaf at epoch e+1. + trillian.MapLeafInclusion new_leaf = 2; +} + +// NotMatchingMapRootProof contains all data necessary to reproduce that set of +// mutations does not produce new expected map root. +message NotMatchingMapRootProof { + // map_root contains the map root hash the monitor observed. + trillian.SignedMapRoot map_root = 1; + // old_leafs is a list of inclusion proofs for the leafs in epoch e. + repeated trillian.MapLeafInclusion old_leafs = 2; + // new_leafs is a list of inclusion proofs for changed leafs (from epoch e + // to epoch e+1). Hashing these produces a different hash than root hash in + // above's map_root. + repeated trillian.MapLeafInclusion new_leafs = 3; +} + +message GetMonitoringResponse { + // smr contains the map root for the sparse Merkle Tree signed with the + // monitor's key on success. If the checks were not successful the + // smr will be empty. The epochs are encoded into the smr map_revision. + trillian.SignedMapRoot smr = 1; + + // seen_timestamp_nanos contains the time in nanoseconds where this particular + // signed map root was retrieved and processed. The actual timestamp of the + // smr returned by the server is contained in above smr field. + int64 seen_timestamp_nanos = 2; + + // + // The following fields provide more information about each failure in this + // response, if any. + // + + // isValid signals if all verification steps for the requested epoch passed + // or not. + bool isValid = 3; + // invalidMapSigProof contains the signed map root received by the + // key-transparency server, if and only if the key-server's signature was + // invalid. + trillian.SignedMapRoot invalidMapSigProof = 4; + // invalidLogSigProof contains the signed map root received by the + // key-transparency server, if and only if the key-server's signature was + // invalid. + trillian.SignedLogRoot invalidLogSigProof = 5; + // invalidMutation contains data to reproduce that there was an invalid + // mutation from epoch e to epoch e+1 + InvalidMutation invalidMutation = 6; + // NotMatchingMapRoot contains all data to reproduce that the set of mutations + // does not produce observed new map root. + NotMatchingMapRootProof notMatchingMapRoot = 7; +} \ No newline at end of file diff --git a/deploy/kubernetes/keytransparency-deployment.yml.tmpl b/deploy/kubernetes/keytransparency-deployment.yml.tmpl index e82ccc1e8..2f6feb0e5 100644 --- a/deploy/kubernetes/keytransparency-deployment.yml.tmpl +++ b/deploy/kubernetes/keytransparency-deployment.yml.tmpl @@ -120,6 +120,41 @@ spec: selector: run: kt-signer --- +------ + apiVersion: apps/v1beta1 + kind: Deployment + metadata: + name: kt-monitor + spec: + strategy: + type: Recreate + template: + metadata: + labels: + run: kt-monitor + spec: + containers: + - name: kt-monitor + image: us.gcr.io/key-transparency/keytransparency-monitor + ports: + - containerPort: 8099 + name: json-grpc + --- + apiVersion: v1 + kind: Service + metadata: + name: kt-monitor + labels: + run: kt-monitor + spec: + type: NodePort + ports: + - port: 8099 + targetPort: 8099 + name: http + selector: + run: kt-monitor + --- apiVersion: v1 kind: Service metadata: diff --git a/docker-compose.yml b/docker-compose.yml index ef73c00cc..35a79340d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -149,3 +149,29 @@ services: - --max-period=5m - --alsologtostderr - --v=5 + + kt-monitor: + depends_on: + - kt-server + - kt-signer + build: + context: .. + dockerfile: ./keytransparency/cmd/keytransparency-monitor/Dockerfile + entrypoint: + - /go/bin/keytransparency-monitor + - --addr=0.0.0.0:8099 + - --kt-url=kt-server:8080 + - --poll-period=5s + - --tls-key=genfiles/server.key + - --tls-cert=genfiles/server.crt + - --sign-key=/kt/monitor_sign-key.pem + - --password=towel + # TODO(ismail): this needs to be the public-key returned by the createTree operation: + - --map-key=trillian/testdata/map-rpc-server.pubkey.pem + - --log-key=trillian/testdata/log-rpc-server.pubkey.pem + - --alsologtostderr + - --v=3 + image: us.gcr.io/key-transparency/keytransparency-monitor + restart: always + ports: + - "8099:8099" # gRPC / HTTPS diff --git a/impl/monitor/monitor.go b/impl/monitor/monitor.go new file mode 100644 index 000000000..d33943c15 --- /dev/null +++ b/impl/monitor/monitor.go @@ -0,0 +1,184 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package monitor implements the monitor service. A monitor repeatedly polls a +// key-transparency server's Mutations API and signs Map Roots if it could +// reconstruct +// clients can query. +package monitor + +import ( + "bytes" + "errors" + "time" + + "github.com/golang/glog" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + "github.com/google/trillian" + + tcrypto "github.com/google/trillian/crypto" + + cmon "github.com/google/keytransparency/core/monitor" + ktpb "github.com/google/keytransparency/core/proto/keytransparency_v1_types" + mopb "github.com/google/keytransparency/core/proto/monitor_v1_types" + + mupb "github.com/google/keytransparency/impl/proto/mutation_v1_service" +) + +// Each page contains pageSize profiles. Each profile contains multiple +// keys. Assuming 2 keys per profile (each of size 2048-bit), a page of +// size 16 will contain about 8KB of data. +const pageSize = 16 + +var ( + // ErrNothingProcessed occurs when the monitor did not process any mutations / + // smrs yet. + ErrNothingProcessed = errors.New("did not process any mutations yet") +) + +// Server holds internal state for the monitor server. +type Server struct { + client mupb.MutationServiceClient + pollPeriod time.Duration + + monitor *cmon.Monitor + signer *tcrypto.Signer + proccessedSMRs []*mopb.GetMonitoringResponse +} + +// New creates a new instance of the monitor server. +func New(cli mupb.MutationServiceClient, + signer *tcrypto.Signer, + logTree, mapTree *trillian.Tree, + poll time.Duration) *Server { + return &Server{ + client: cli, + pollPeriod: poll, + // TODO(ismail) use domain info to properly init. the monitor: + monitor: &cmon.Monitor{}, + signer: signer, + proccessedSMRs: make([]*mopb.GetMonitoringResponse, 256), + } +} + +// StartPolling initiates polling and processing mutations every pollPeriod. +func (s *Server) StartPolling() error { + t := time.NewTicker(s.pollPeriod) + for now := range t.C { + glog.Infof("Polling: %v", now) + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + if _, err := s.pollMutations(ctx); err != nil { + glog.Errorf("pollMutations(_): %v", err) + } + } + return nil +} + +// GetSignedMapRoot returns the latest valid signed map root the monitor +// observed. Additionally, the response contains additional data necessary to +// reproduce errors on failure. +// +// Returns the signed map root for the latest epoch the monitor observed. If +// the monitor could not reconstruct the map root given the set of mutations +// from the previous to the current epoch it won't sign the map root and +// additional data will be provided to reproduce the failure. +func (s *Server) GetSignedMapRoot(ctx context.Context, in *mopb.GetMonitoringRequest) (*mopb.GetMonitoringResponse, error) { + if len(s.proccessedSMRs) > 0 { + return s.proccessedSMRs[len(s.proccessedSMRs)-1], nil + } + return nil, ErrNothingProcessed +} + +// GetSignedMapRootByRevision works similar to GetSignedMapRoot but returns +// the monitor's result for a specific map revision. +// +// Returns the signed map root for the specified epoch the monitor observed. +// If the monitor could not reconstruct the map root given the set of +// mutations from the previous to the current epoch it won't sign the map root +// and additional data will be provided to reproduce the failure. +func (s *Server) GetSignedMapRootByRevision(ctx context.Context, in *mopb.GetMonitoringRequest) (*mopb.GetMonitoringResponse, error) { + // TODO(ismail): implement by revision API + return nil, grpc.Errorf(codes.Unimplemented, "GetSignedMapRoot is unimplemented") +} + +func (s *Server) pollMutations(ctx context.Context, opts ...grpc.CallOption) ([]*ktpb.Mutation, error) { + // TODO(ismail): move everything that does not rely on impl packages (e.g., + // the client here) into core: + resp, err := s.client.GetMutations(ctx, &ktpb.GetMutationsRequest{ + PageSize: pageSize, + Epoch: s.nextRevToQuery(), + }, opts...) + if err != nil { + return nil, err + } + + if got, want := resp.GetSmr(), s.lastSeenSMR(); bytes.Equal(got.GetRootHash(), want.GetRootHash()) && + got.GetMapRevision() == want.GetMapRevision() { + // We already processed this SMR. Do not update seen SMRs. Do not scroll + // pages for further mutations. Return empty mutations list. + glog.Infof("Already processed this SMR with revision %v. Continuing.", got.GetMapRevision()) + return nil, nil + } + + mutations, err := s.pageMutations(ctx, resp, opts...) + if err != nil { + glog.Errorf("s.pageMutations(_): %v", err) + return nil, err + } + + // TODO(Ismail): let the verification method in core directly return the response + monitorResp := s.monitor.VerifyResponse(resp, mutations) + // Update seen/processed signed map roots: + s.proccessedSMRs = append(s.proccessedSMRs, monitorResp) + + return mutations, nil +} + +// pageMutations iterates/pages through all mutations in the case there were +// more then maximum pageSize mutations in between epochs. +// It will modify the passed GetMutationsResponse resp. +func (s *Server) pageMutations(ctx context.Context, resp *ktpb.GetMutationsResponse, + opts ...grpc.CallOption) ([]*ktpb.Mutation, error) { + ms := make([]*ktpb.Mutation, pageSize*2) + ms = append(ms, resp.GetMutations()...) + + // Query all mutations in the current epoch + for resp.GetNextPageToken() != "" { + req := &ktpb.GetMutationsRequest{PageSize: pageSize} + resp, err := s.client.GetMutations(ctx, req, opts...) + if err != nil { + return nil, err + } + ms = append(ms, resp.GetMutations()...) + } + return ms, nil +} + +func (s *Server) lastSeenSMR() *trillian.SignedMapRoot { + if len(s.proccessedSMRs) > 0 { + return s.proccessedSMRs[len(s.proccessedSMRs)-1].GetSmr() + } + return nil +} + +func (s *Server) nextRevToQuery() int64 { + smr := s.lastSeenSMR() + if smr == nil { + return 1 + } + return smr.GetMapRevision() + 1 +} diff --git a/impl/monitor/monitor_test.go b/impl/monitor/monitor_test.go new file mode 100644 index 000000000..6464d5386 --- /dev/null +++ b/impl/monitor/monitor_test.go @@ -0,0 +1,32 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package monitor implements the monitor service. A monitor repeatedly polls a +// key-transparency server's Mutations API and signs Map Roots if it could +// reconstruct +// clients can query. +package monitor + +import ( + "golang.org/x/net/context" + "testing" +) + +func TestGetSignedMapRoot(t *testing.T) { + srv := Server{} + _, err := srv.GetSignedMapRoot(context.TODO(), nil) + if got, want := err, ErrNothingProcessed; got != want { + t.Errorf("GetSignedMapRoot(_, _): %v, want %v", got, want) + } +} diff --git a/impl/mutation/mutation.go b/impl/mutation/mutation.go index 9fae43072..8b816ef12 100644 --- a/impl/mutation/mutation.go +++ b/impl/mutation/mutation.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package mutation implements the monitor service. +// Package mutation implements the mutations service a monitor can query. package mutation import ( diff --git a/impl/proto/monitor_v1_service/gen.go b/impl/proto/monitor_v1_service/gen.go new file mode 100644 index 000000000..250a32e56 --- /dev/null +++ b/impl/proto/monitor_v1_service/gen.go @@ -0,0 +1,19 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:generate protoc -I=. -I=$GOPATH/src/ -I=$GOPATH/src/github.com/google/trillian/ -I=$GOPATH/src/github.com/googleapis/googleapis/ --go_out=,plugins=grpc:. monitor_v1_service.proto + +//go:generate protoc -I=. -I=$GOPATH/src/ -I=$GOPATH/src/github.com/google/trillian/ -I=$GOPATH/src/github.com/googleapis/googleapis/ --grpc-gateway_out=logtostderr=true:. monitor_v1_service.proto + +package monitor_v1_service diff --git a/impl/proto/monitor_v1_service/monitor_v1_service.pb.go b/impl/proto/monitor_v1_service/monitor_v1_service.pb.go new file mode 100644 index 000000000..66c84e6aa --- /dev/null +++ b/impl/proto/monitor_v1_service/monitor_v1_service.pb.go @@ -0,0 +1,197 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: monitor_v1_service.proto + +/* +Package monitor_v1_service is a generated protocol buffer package. + +Monitor Service + +The Key Transparency monitor server service consists of APIs to fetch +monitor results queried using the mutations API. + +It is generated from these files: + monitor_v1_service.proto + +It has these top-level messages: +*/ +package monitor_v1_service + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import monitor_v1_types "github.com/google/keytransparency/core/proto/monitor_v1_types" +import _ "google.golang.org/genproto/googleapis/api/annotations" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for MonitorService service + +type MonitorServiceClient interface { + // GetSignedMapRoot returns the latest valid signed map root the monitor + // observed. Additionally, the response contains additional data necessary to + // reproduce errors on failure. + // + // Returns the signed map root for the latest epoch the monitor observed. If + // the monitor could not reconstruct the map root given the set of mutations + // from the previous to the current epoch it won't sign the map root and + // additional data will be provided to reproduce the failure. + GetSignedMapRoot(ctx context.Context, in *monitor_v1_types.GetMonitoringRequest, opts ...grpc.CallOption) (*monitor_v1_types.GetMonitoringResponse, error) + // GetSignedMapRootByRevision works similar to GetSignedMapRoot but returns + // the monitor's result for a specific map revision. + // + // Returns the signed map root for the specified epoch the monitor observed. + // If the monitor could not reconstruct the map root given the set of + // mutations from the previous to the current epoch it won't sign the map root + // and additional data will be provided to reproduce the failure. + GetSignedMapRootByRevision(ctx context.Context, in *monitor_v1_types.GetMonitoringRequest, opts ...grpc.CallOption) (*monitor_v1_types.GetMonitoringResponse, error) +} + +type monitorServiceClient struct { + cc *grpc.ClientConn +} + +func NewMonitorServiceClient(cc *grpc.ClientConn) MonitorServiceClient { + return &monitorServiceClient{cc} +} + +func (c *monitorServiceClient) GetSignedMapRoot(ctx context.Context, in *monitor_v1_types.GetMonitoringRequest, opts ...grpc.CallOption) (*monitor_v1_types.GetMonitoringResponse, error) { + out := new(monitor_v1_types.GetMonitoringResponse) + err := grpc.Invoke(ctx, "/monitor.v1.service.MonitorService/GetSignedMapRoot", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *monitorServiceClient) GetSignedMapRootByRevision(ctx context.Context, in *monitor_v1_types.GetMonitoringRequest, opts ...grpc.CallOption) (*monitor_v1_types.GetMonitoringResponse, error) { + out := new(monitor_v1_types.GetMonitoringResponse) + err := grpc.Invoke(ctx, "/monitor.v1.service.MonitorService/GetSignedMapRootByRevision", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for MonitorService service + +type MonitorServiceServer interface { + // GetSignedMapRoot returns the latest valid signed map root the monitor + // observed. Additionally, the response contains additional data necessary to + // reproduce errors on failure. + // + // Returns the signed map root for the latest epoch the monitor observed. If + // the monitor could not reconstruct the map root given the set of mutations + // from the previous to the current epoch it won't sign the map root and + // additional data will be provided to reproduce the failure. + GetSignedMapRoot(context.Context, *monitor_v1_types.GetMonitoringRequest) (*monitor_v1_types.GetMonitoringResponse, error) + // GetSignedMapRootByRevision works similar to GetSignedMapRoot but returns + // the monitor's result for a specific map revision. + // + // Returns the signed map root for the specified epoch the monitor observed. + // If the monitor could not reconstruct the map root given the set of + // mutations from the previous to the current epoch it won't sign the map root + // and additional data will be provided to reproduce the failure. + GetSignedMapRootByRevision(context.Context, *monitor_v1_types.GetMonitoringRequest) (*monitor_v1_types.GetMonitoringResponse, error) +} + +func RegisterMonitorServiceServer(s *grpc.Server, srv MonitorServiceServer) { + s.RegisterService(&_MonitorService_serviceDesc, srv) +} + +func _MonitorService_GetSignedMapRoot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(monitor_v1_types.GetMonitoringRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MonitorServiceServer).GetSignedMapRoot(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/monitor.v1.service.MonitorService/GetSignedMapRoot", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MonitorServiceServer).GetSignedMapRoot(ctx, req.(*monitor_v1_types.GetMonitoringRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _MonitorService_GetSignedMapRootByRevision_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(monitor_v1_types.GetMonitoringRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MonitorServiceServer).GetSignedMapRootByRevision(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/monitor.v1.service.MonitorService/GetSignedMapRootByRevision", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MonitorServiceServer).GetSignedMapRootByRevision(ctx, req.(*monitor_v1_types.GetMonitoringRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _MonitorService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "monitor.v1.service.MonitorService", + HandlerType: (*MonitorServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetSignedMapRoot", + Handler: _MonitorService_GetSignedMapRoot_Handler, + }, + { + MethodName: "GetSignedMapRootByRevision", + Handler: _MonitorService_GetSignedMapRootByRevision_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "monitor_v1_service.proto", +} + +func init() { proto.RegisterFile("monitor_v1_service.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 261 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x90, 0xaf, 0x4e, 0x03, 0x41, + 0x10, 0xc6, 0xd3, 0x0a, 0xc4, 0x09, 0x42, 0x36, 0xfc, 0xcb, 0xa5, 0x18, 0x04, 0xb8, 0x9b, 0x14, + 0x1c, 0x12, 0x53, 0x55, 0x73, 0xc5, 0x37, 0xdb, 0x63, 0xb2, 0x6c, 0xe8, 0xcd, 0x2c, 0x3b, 0xd3, + 0x4d, 0x2e, 0x04, 0x01, 0x1a, 0xc7, 0x23, 0xf0, 0x48, 0xbc, 0x02, 0x0f, 0x42, 0x72, 0xb7, 0xa2, + 0x80, 0xc0, 0x60, 0xe7, 0x9b, 0xef, 0x37, 0xbf, 0x4c, 0x71, 0xdc, 0x32, 0x79, 0xe5, 0xb8, 0x4c, + 0xd3, 0xa5, 0x60, 0x4c, 0xbe, 0xc1, 0x2a, 0x44, 0x56, 0x36, 0x26, 0x27, 0x55, 0x9a, 0x56, 0x39, + 0x29, 0x6f, 0x9c, 0xd7, 0xbb, 0xcd, 0xaa, 0x6a, 0xb8, 0x05, 0xc7, 0xec, 0xd6, 0x08, 0xf7, 0xd8, + 0x69, 0xb4, 0x24, 0xc1, 0x46, 0xa4, 0xa6, 0x83, 0x86, 0x23, 0x42, 0x4f, 0x80, 0x2d, 0xb4, 0x76, + 0x01, 0xe5, 0xd7, 0x60, 0xb8, 0x54, 0x4e, 0x32, 0xca, 0x06, 0x0f, 0x96, 0x88, 0xd5, 0xaa, 0x67, + 0xca, 0xe9, 0xc5, 0xfb, 0xb8, 0xd8, 0x9d, 0x0f, 0xc5, 0xc5, 0xa0, 0x61, 0x9e, 0x47, 0xc5, 0xde, + 0x0c, 0x75, 0xe1, 0x1d, 0xe1, 0xed, 0xdc, 0x86, 0x9a, 0x59, 0xcd, 0x59, 0xb5, 0x25, 0x3c, 0xe0, + 0x67, 0xa8, 0xb9, 0xe9, 0xc9, 0xd5, 0xf8, 0xb0, 0x41, 0xd1, 0xf2, 0xfc, 0xcf, 0x3d, 0x09, 0x4c, + 0x82, 0xa7, 0x93, 0x97, 0x8f, 0xcf, 0xb7, 0xf1, 0xa1, 0xd9, 0x87, 0x34, 0x85, 0xd6, 0x06, 0x88, + 0xcc, 0x2a, 0x57, 0x6b, 0xab, 0x28, 0x6a, 0x5e, 0x47, 0x45, 0xf9, 0xd3, 0xe1, 0xba, 0xab, 0x31, + 0x79, 0xf1, 0x4c, 0xff, 0x6f, 0x73, 0xd2, 0xdb, 0x1c, 0x99, 0x83, 0x6f, 0x36, 0xf0, 0x28, 0x6a, + 0xa3, 0x3e, 0xad, 0x76, 0xfa, 0x67, 0x5d, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, 0x6d, 0x85, 0xab, + 0x1e, 0xd0, 0x01, 0x00, 0x00, +} diff --git a/impl/proto/monitor_v1_service/monitor_v1_service.pb.gw.go b/impl/proto/monitor_v1_service/monitor_v1_service.pb.gw.go new file mode 100644 index 000000000..26786982a --- /dev/null +++ b/impl/proto/monitor_v1_service/monitor_v1_service.pb.gw.go @@ -0,0 +1,174 @@ +// Code generated by protoc-gen-grpc-gateway +// source: monitor_v1_service.proto +// DO NOT EDIT! + +/* +Package monitor_v1_service is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package monitor_v1_service + +import ( + "io" + "net/http" + + "github.com/golang/protobuf/proto" + "github.com/google/keytransparency/core/proto/monitor_v1_types" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" +) + +var _ codes.Code +var _ io.Reader +var _ = runtime.String +var _ = utilities.NewDoubleArray + +var ( + filter_MonitorService_GetSignedMapRoot_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_MonitorService_GetSignedMapRoot_0(ctx context.Context, marshaler runtime.Marshaler, client MonitorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq monitor_v1_types.GetMonitoringRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_MonitorService_GetSignedMapRoot_0); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetSignedMapRoot(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_MonitorService_GetSignedMapRootByRevision_0(ctx context.Context, marshaler runtime.Marshaler, client MonitorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq monitor_v1_types.GetMonitoringRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["start"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "start") + } + + protoReq.Start, err = runtime.Int64(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.GetSignedMapRootByRevision(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +// RegisterMonitorServiceHandlerFromEndpoint is same as RegisterMonitorServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterMonitorServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterMonitorServiceHandler(ctx, mux, conn) +} + +// RegisterMonitorServiceHandler registers the http handlers for service MonitorService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterMonitorServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + client := NewMonitorServiceClient(conn) + + mux.Handle("GET", pattern_MonitorService_GetSignedMapRoot_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_MonitorService_GetSignedMapRoot_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_MonitorService_GetSignedMapRoot_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_MonitorService_GetSignedMapRootByRevision_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_MonitorService_GetSignedMapRootByRevision_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_MonitorService_GetSignedMapRootByRevision_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_MonitorService_GetSignedMapRoot_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "map", "roots"}, "latest")) + + pattern_MonitorService_GetSignedMapRootByRevision_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "map", "roots", "start"}, "")) +) + +var ( + forward_MonitorService_GetSignedMapRoot_0 = runtime.ForwardResponseMessage + + forward_MonitorService_GetSignedMapRootByRevision_0 = runtime.ForwardResponseMessage +) diff --git a/impl/proto/monitor_v1_service/monitor_v1_service.proto b/impl/proto/monitor_v1_service/monitor_v1_service.proto new file mode 100644 index 000000000..509afe90a --- /dev/null +++ b/impl/proto/monitor_v1_service/monitor_v1_service.proto @@ -0,0 +1,61 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +// Monitor Service +// +// The Key Transparency monitor server service consists of APIs to fetch +// monitor results queried using the mutations API. +package monitor.v1.service; + +import "github.com/google/keytransparency/core/proto/monitor_v1_types/monitor_v1_types.proto"; +import "google/api/annotations.proto"; + +// The Monitor Service API allows clients to query the monitors observed and +// validated signed map roots. +// +// - Signed Map Roots can be collected using the GetSignedMapRoot APIs. +// - Monitor resources are named: +// - /v1/map/roots/* +// - /v1/map/roots:latest +// +service MonitorService { + // GetSignedMapRoot returns the latest valid signed map root the monitor + // observed. Additionally, the response contains additional data necessary to + // reproduce errors on failure. + // + // Returns the signed map root for the latest epoch the monitor observed. If + // the monitor could not reconstruct the map root given the set of mutations + // from the previous to the current epoch it won't sign the map root and + // additional data will be provided to reproduce the failure. + rpc GetSignedMapRoot(monitor.v1.types.GetMonitoringRequest) + returns (monitor.v1.types.GetMonitoringResponse) { + option (google.api.http) = { get: "/v1/map/roots:latest" }; + } + + // GetSignedMapRootByRevision works similar to GetSignedMapRoot but returns + // the monitor's result for a specific map revision. + // + // Returns the signed map root for the specified epoch the monitor observed. + // If the monitor could not reconstruct the map root given the set of + // mutations from the previous to the current epoch it won't sign the map root + // and additional data will be provided to reproduce the failure. + rpc GetSignedMapRootByRevision(monitor.v1.types.GetMonitoringRequest) + returns(monitor.v1.types.GetMonitoringResponse) { + option (google.api.http) = { get: "/v1/map/roots/{start}" }; + } +} + + diff --git a/scripts/deploy.sh b/scripts/deploy.sh index d16dc471b..95b8dfda7 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -73,7 +73,7 @@ function pushTrillianImgs() function pushKTImgs() { - images=("keytransparency-server" "prometheus") + images=("keytransparency-server" "keytransparency-monitor" "prometheus") for DOCKER_IMAGE_NAME in "${images[@]}" do # Push the images as we refer to them in the kubernetes config files: diff --git a/scripts/gen_monitor_keys.sh b/scripts/gen_monitor_keys.sh new file mode 100644 index 000000000..09a83ef66 --- /dev/null +++ b/scripts/gen_monitor_keys.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Create output directory. +mkdir -p "${GOPATH}/src/github.com/google/keytransparency/genfiles" +cd "${GOPATH}/src/github.com/google/keytransparency/genfiles" + +INTERACTIVE=1 + +while getopts ":f" opt; do + case $opt in + f) + INTERACTIVE=0 + ;; + \?) + echo "Invalid option: -$OPTARG." + usage + exit 1 + ;; + esac +done + + +DEFAULT_PWD=towel + +# Generate monitor signing key-pair: +if ((INTERACTIVE == 1)); then + # Prompts for password: + openssl ecparam -name prime256v1 -genkey | openssl ec -aes256 -out monitor_sign-key.pem +else + + openssl ecparam -name prime256v1 -genkey | openssl ec -aes256 -passout pass:$DEFAULT_PWD -out monitor_sign-key.pem +fi +chmod 600 monitor_sign-key.pem + diff --git a/scripts/prepare_server.sh b/scripts/prepare_server.sh index d7a2a745f..e1e4b2ace 100755 --- a/scripts/prepare_server.sh +++ b/scripts/prepare_server.sh @@ -23,6 +23,7 @@ INTERACTIVE=1 FRONTEND=1 FRONTENDNUM=0 BACKEND=1 +MONITOR=1 # 1 means SQLite, 2 means MySQL DSN="" IP1="127.0.0.1" @@ -131,6 +132,10 @@ if ((FRONTEND == 1)); then ./scripts/gen_server_keys.sh -d "${CERTDOMAIN}" -a "${CERTIP}" -s "${SAN_DNS}" fi +if ((MONITOR == 1)); then + ./scripts/gen_monitor_keys.sh +fi + # Generating .env file ENV="SIGN_PERIOD=5"